summaryrefslogtreecommitdiffstats
path: root/text-utils
diff options
context:
space:
mode:
Diffstat (limited to 'text-utils')
-rw-r--r--text-utils/Makemodule.am98
-rw-r--r--text-utils/col.1158
-rw-r--r--text-utils/col.c601
-rw-r--r--text-utils/colcrt.1105
-rw-r--r--text-utils/colcrt.c296
-rw-r--r--text-utils/colrm.175
-rw-r--r--text-utils/colrm.c193
-rw-r--r--text-utils/column.1186
-rw-r--r--text-utils/column.c858
-rw-r--r--text-utils/hexdump-conv.c111
-rw-r--r--text-utils/hexdump-display.c454
-rw-r--r--text-utils/hexdump-parse.c647
-rw-r--r--text-utils/hexdump.1382
-rw-r--r--text-utils/hexdump.c261
-rw-r--r--text-utils/hexdump.h113
-rw-r--r--text-utils/line.117
-rw-r--r--text-utils/line.c83
-rw-r--r--text-utils/more.1258
-rw-r--r--text-utils/more.c2097
-rw-r--r--text-utils/pg.1238
-rw-r--r--text-utils/pg.c1694
-rw-r--r--text-utils/rev.163
-rw-r--r--text-utils/rev.c186
-rw-r--r--text-utils/ul.1113
-rw-r--r--text-utils/ul.c653
25 files changed, 9940 insertions, 0 deletions
diff --git a/text-utils/Makemodule.am b/text-utils/Makemodule.am
new file mode 100644
index 0000000..6c4b520
--- /dev/null
+++ b/text-utils/Makemodule.am
@@ -0,0 +1,98 @@
+if BUILD_COL
+usrbin_exec_PROGRAMS += col
+dist_man_MANS += text-utils/col.1
+col_SOURCES = text-utils/col.c
+col_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_COLCRT
+usrbin_exec_PROGRAMS += colcrt
+dist_man_MANS += text-utils/colcrt.1
+colcrt_SOURCES = text-utils/colcrt.c
+endif
+
+if BUILD_COLRM
+usrbin_exec_PROGRAMS += colrm
+dist_man_MANS += text-utils/colrm.1
+colrm_SOURCES = text-utils/colrm.c
+colrm_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_COLUMN
+usrbin_exec_PROGRAMS += column
+dist_man_MANS += text-utils/column.1
+column_SOURCES = text-utils/column.c
+column_LDADD = $(LDADD) libcommon.la libsmartcols.la
+column_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+if BUILD_HEXDUMP
+usrbin_exec_PROGRAMS += hexdump
+dist_man_MANS += text-utils/hexdump.1
+hexdump_SOURCES = \
+ text-utils/hexdump-conv.c \
+ text-utils/hexdump-display.c \
+ text-utils/hexdump.c \
+ text-utils/hexdump.h \
+ text-utils/hexdump-parse.c
+hexdump_LDADD = $(LDADD) libcommon.la libtcolors.la
+endif
+
+if BUILD_REV
+usrbin_exec_PROGRAMS += rev
+dist_man_MANS += text-utils/rev.1
+rev_SOURCES = text-utils/rev.c
+endif
+
+if BUILD_LINE
+usrbin_exec_PROGRAMS += line
+line_SOURCES = text-utils/line.c
+dist_man_MANS += text-utils/line.1
+endif
+
+if BUILD_PG
+usrbin_exec_PROGRAMS += pg
+dist_man_MANS += text-utils/pg.1
+pg_SOURCES = text-utils/pg.c
+pg_CFLAGS = $(AM_CFLAGS) $(BSD_WARN_CFLAGS) $(NCURSES_CFLAGS) $(TINFO_CFLAGS)
+pg_LDADD = $(LDADD) libcommon.la $(NCURSES_LIBS) $(TINFO_LIBS)
+endif # BUILD_PG
+
+
+if BUILD_UL
+usrbin_exec_PROGRAMS += ul
+dist_man_MANS += text-utils/ul.1
+ul_SOURCES = text-utils/ul.c
+ul_CFLAGS = $(AM_CFLAGS)
+ul_LDADD = $(LDADD)
+if HAVE_TINFO
+ul_LDADD += $(TINFO_LIBS)
+ul_LDADD += $(TINFO_CFLAGS)
+else
+ul_CFLAGS += $(NCURSES_CFLAGS)
+ul_LDADD += $(NCURSES_LIBS)
+endif
+endif # BUILD_UL
+
+
+if BUILD_MORE
+bin_PROGRAMS += more
+dist_man_MANS += text-utils/more.1
+more_SOURCES = text-utils/more.c
+more_CFLAGS = $(AM_CFLAGS) $(BSD_WARN_CFLAGS)
+more_LDADD = $(LDADD) $(MAGIC_LIBS) libcommon.la
+if HAVE_TINFO
+more_LDADD += $(TINFO_LIBS)
+more_LDADD += $(TINFO_CFLAGS)
+else
+more_CFLAGS += $(NCURSES_CFLAGS)
+more_LDADD += $(NCURSES_LIBS)
+endif
+
+check_PROGRAMS += test_more
+test_more_SOURCES = $(more_SOURCES)
+test_more_CFLAGS = -DTEST_PROGRAM $(more_CFLAGS)
+test_more_LDADD = $(more_LDADD)
+
+endif # BUILD_MORE
+
diff --git a/text-utils/col.1 b/text-utils/col.1
new file mode 100644
index 0000000..cdd5c02
--- /dev/null
+++ b/text-utils/col.1
@@ -0,0 +1,158 @@
+.\" Copyright (c) 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Michael Rendell.
+.\"
+.\" 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)col.1 6.8 (Berkeley) 6/17/91
+.\"
+.TH COL "1" "July 2014" "util-linux" "User Commands"
+.SH NAME
+col \- filter reverse line feeds from input
+.SH SYNOPSIS
+.B col
+[options]
+.SH DESCRIPTION
+.B col
+filters out reverse (and half-reverse) line feeds so the output is in the
+correct order, with only forward and half-forward line feeds. It also replaces
+any whitespace characters with tabs where possible. This can be useful in
+processing the output of
+.BR nroff (1)
+and
+.BR tbl (1).
+.PP
+.B col
+reads from standard input and writes to standard output.
+.SH OPTIONS
+.TP
+\fB\-b\fR, \fB\-\-no\-backspaces\fR
+Do not output any backspaces, printing only the last character written to
+each column position.
+.TP
+\fB\-f\fR, \fB\-\-fine\fR
+Permit half-forward line feeds.
+Normally characters destined for a half-line boundary are printed on the
+following line.
+.TP
+\fB\-h\fR, \fB\-\-tabs\fR
+Output tabs instead of multiple spaces.
+.TP
+\fB\-l\fR, \fB\-\-lines\fR \fInumber\fR
+Buffer at least
+.I number
+lines in memory. By default, 128 lines are buffered.
+.TP
+\fB\-p\fR, \fB\-\-pass\fR
+Force unknown control sequences to be passed through unchanged. Normally
+.B col
+will filter out any control sequences other than those
+recognized and interpreted by itself, which are listed below.
+.TP
+\fB\-x\fR, \fB\-\-spaces\fR
+Output multiple spaces instead of tabs.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-H\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH CONFORMING TO
+The
+.B col
+utility conforms to the Single UNIX Specification, Version 2. The
+.B \-l
+option is an extension to the standard.
+.SH NOTES
+The control sequences for carriage motion that
+.B col
+understands and their decimal values are listed in the following table:
+.PP
+.RS
+.PD 0
+.TP 18
+.B ESC\-7
+reverse line feed (escape then 7)
+.TP
+.B ESC\-8
+half reverse line feed (escape then 8)
+.TP
+.B ESC\-9
+half forward line feed (escape then 9)
+.TP
+.B backspace
+moves back one column (8); ignored in the first column
+.TP
+.B newline
+forward line feed (10); also does carriage return
+.TP
+.B carriage return
+(13)
+.TP
+.B shift in
+shift to normal character set (15)
+.TP
+.B shift out
+shift to alternate character set (14)
+.TP
+.B space
+moves forward one column (32)
+.TP
+.B tab
+moves forward to next tab stop (9)
+.TP
+.B vertical tab
+reverse line feed (11)
+.PD
+.RE
+.PP
+All unrecognized control characters and escape sequences are discarded.
+.PP
+.B col
+keeps track of the character set as characters are read and makes sure the
+character set is correct when they are output.
+.PP
+If the input attempts to back up to the last flushed line,
+.B col
+will display a warning message.
+.SH HISTORY
+A
+.B col
+command appeared in Version 6 AT&T UNIX.
+.SH SEE ALSO
+.BR expand (1),
+.BR nroff (1),
+.BR tbl (1)
+.SH AVAILABILITY
+The col command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/text-utils/col.c b/text-utils/col.c
new file mode 100644
index 0000000..66fc819
--- /dev/null
+++ b/text-utils/col.c
@@ -0,0 +1,601 @@
+/*-
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Rendell of the Memorial University of Newfoundland.
+ *
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ *
+ * Wed Jun 22 22:15:41 1994, faith@cs.unc.edu: Added internationalization
+ * patches from Andries.Brouwer@cwi.nl
+ * Wed Sep 14 22:31:17 1994: patches from Carl Christofferson
+ * (cchris@connected.com)
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * added Native Language Support
+ * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
+ * modified to work correctly in multi-byte locales
+ *
+ */
+
+/*
+ * This command is deprecated. The utility is in maintenance mode,
+ * meaning we keep them in source tree for backward compatibility
+ * only. Do not waste time making this command better, unless the
+ * fix is about security or other very critical issue.
+ *
+ * See Documentation/deprecated.txt for more information.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "nls.h"
+#include "xalloc.h"
+#include "widechar.h"
+#include "strutils.h"
+#include "closestream.h"
+
+#define BS '\b' /* backspace */
+#define TAB '\t' /* tab */
+#define SPACE ' ' /* space */
+#define NL '\n' /* newline */
+#define CR '\r' /* carriage return */
+#define ESC '\033' /* escape */
+#define SI '\017' /* shift in to normal character set */
+#define SO '\016' /* shift out to alternate character set */
+#define VT '\013' /* vertical tab (aka reverse line feed) */
+#define RLF '\007' /* ESC-07 reverse line feed */
+#define RHLF '\010' /* ESC-010 reverse half-line feed */
+#define FHLF '\011' /* ESC-011 forward half-line feed */
+
+/* build up at least this many lines before flushing them out */
+#define BUFFER_MARGIN 32
+
+typedef char CSET;
+
+typedef struct char_str {
+#define CS_NORMAL 1
+#define CS_ALTERNATE 2
+ int c_column; /* column character is in */
+ CSET c_set; /* character set (currently only 2) */
+ wchar_t c_char; /* character in question */
+ int c_width; /* character width */
+} CHAR;
+
+typedef struct line_str LINE;
+struct line_str {
+ CHAR *l_line; /* characters on the line */
+ LINE *l_prev; /* previous line */
+ LINE *l_next; /* next line */
+ int l_lsize; /* allocated sizeof l_line */
+ int l_line_len; /* strlen(l_line) */
+ int l_needs_sort; /* set if chars went in out of order */
+ int l_max_col; /* max column in the line */
+};
+
+void free_line(LINE *l);
+void flush_line(LINE *l);
+void flush_lines(int);
+void flush_blanks(void);
+LINE *alloc_line(void);
+
+static CSET last_set; /* char_set of last char printed */
+static LINE *lines;
+static int compress_spaces; /* if doing space -> tab conversion */
+static int fine; /* if `fine' resolution (half lines) */
+static unsigned max_bufd_lines; /* max # lines to keep in memory */
+static int nblank_lines; /* # blanks after last flushed line */
+static int no_backspaces; /* if not to output any backspaces */
+static int pass_unknown_seqs; /* whether to pass unknown control sequences */
+
+#define PUTC(ch) \
+ if (putwchar(ch) == WEOF) \
+ wrerr();
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fprintf(out, _(
+ "\nUsage:\n"
+ " %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Filter out reverse line feeds.\n"), out);
+
+ fprintf(out, _(
+ "\nOptions:\n"
+ " -b, --no-backspaces do not output backspaces\n"
+ " -f, --fine permit forward half line feeds\n"
+ " -p, --pass pass unknown control sequences\n"
+ " -h, --tabs convert spaces to tabs\n"
+ " -x, --spaces convert tabs to spaces\n"
+ " -l, --lines NUM buffer at least NUM lines\n"
+ ));
+ printf( " -H, --help %s\n", USAGE_OPTSTR_HELP);
+ printf( " -V, --version %s\n", USAGE_OPTSTR_VERSION);
+
+ fputs(USAGE_SEPARATOR, out);
+ fprintf(out, _(
+ "%s reads from standard input and writes to standard output\n\n"),
+ program_invocation_short_name);
+
+ printf(USAGE_MAN_TAIL("col(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static void __attribute__((__noreturn__)) wrerr(void)
+{
+ errx(EXIT_FAILURE, _("write error"));
+}
+
+int main(int argc, char **argv)
+{
+ register wint_t ch;
+ CHAR *c = NULL;
+ CSET cur_set; /* current character set */
+ LINE *l; /* current line */
+ int extra_lines; /* # of lines above first line */
+ int cur_col; /* current column */
+ int cur_line; /* line number of current position */
+ int max_line; /* max value of cur_line */
+ int this_line; /* line l points to */
+ int nflushd_lines; /* number of lines that were flushed */
+ int adjust, opt, warned;
+ int ret = EXIT_SUCCESS;
+
+ static const struct option longopts[] = {
+ { "no-backspaces", no_argument, NULL, 'b' },
+ { "fine", no_argument, NULL, 'f' },
+ { "pass", no_argument, NULL, 'p' },
+ { "tabs", no_argument, NULL, 'h' },
+ { "spaces", no_argument, NULL, 'x' },
+ { "lines", required_argument, NULL, 'l' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'H' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ max_bufd_lines = 128 * 2;
+ compress_spaces = 1; /* compress spaces into tabs */
+ pass_unknown_seqs = 0; /* remove unknown escape sequences */
+
+ while ((opt = getopt_long(argc, argv, "bfhl:pxVH", longopts, NULL)) != -1)
+ switch (opt) {
+ case 'b': /* do not output backspaces */
+ no_backspaces = 1;
+ break;
+ case 'f': /* allow half forward line feeds */
+ fine = 1;
+ break;
+ case 'h': /* compress spaces into tabs */
+ compress_spaces = 1;
+ break;
+ case 'l':
+ /*
+ * Buffered line count, which is a value in half
+ * lines e.g. twice the amount specified.
+ */
+ max_bufd_lines = strtou32_or_err(optarg, _("bad -l argument")) * 2;
+ break;
+ case 'p':
+ pass_unknown_seqs = 1;
+ break;
+ case 'x': /* do not compress spaces into tabs */
+ compress_spaces = 0;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'H':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (optind != argc) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ adjust = cur_col = extra_lines = warned = 0;
+ cur_line = max_line = nflushd_lines = this_line = 0;
+ cur_set = last_set = CS_NORMAL;
+ lines = l = alloc_line();
+
+ while (feof(stdin) == 0) {
+ errno = 0;
+ if ((ch = getwchar()) == WEOF) {
+ if (errno == EILSEQ) {
+ warn(_("failed on line %d"), max_line + 1);
+ ret = EXIT_FAILURE;
+ }
+ break;
+ }
+ if (!iswgraph(ch)) {
+ switch (ch) {
+ case BS: /* can't go back further */
+ if (cur_col == 0)
+ continue;
+ if (c)
+ cur_col -= c->c_width;
+ else
+ cur_col--;
+ continue;
+ case CR:
+ cur_col = 0;
+ continue;
+ case ESC: /* just ignore EOF */
+ switch(getwchar()) {
+ case RLF:
+ cur_line -= 2;
+ break;
+ case RHLF:
+ cur_line--;
+ break;
+ case FHLF:
+ cur_line++;
+ if (cur_line > max_line)
+ max_line = cur_line;
+ }
+ continue;
+ case NL:
+ cur_line += 2;
+ if (cur_line > max_line)
+ max_line = cur_line;
+ cur_col = 0;
+ continue;
+ case SPACE:
+ ++cur_col;
+ continue;
+ case SI:
+ cur_set = CS_NORMAL;
+ continue;
+ case SO:
+ cur_set = CS_ALTERNATE;
+ continue;
+ case TAB: /* adjust column */
+ cur_col |= 7;
+ ++cur_col;
+ continue;
+ case VT:
+ cur_line -= 2;
+ continue;
+ }
+ if (iswspace(ch)) {
+ if (wcwidth(ch) > 0)
+ cur_col += wcwidth(ch);
+ continue;
+ }
+ if (!pass_unknown_seqs)
+ continue;
+ }
+
+ /* Must stuff ch in a line - are we at the right one? */
+ if (cur_line != this_line - adjust) {
+ LINE *lnew;
+ int nmove;
+
+ adjust = 0;
+ nmove = cur_line - this_line;
+ if (!fine) {
+ /* round up to next line */
+ if (cur_line & 1) {
+ adjust = 1;
+ nmove++;
+ }
+ }
+ if (nmove < 0) {
+ for (; nmove < 0 && l->l_prev; nmove++)
+ l = l->l_prev;
+ if (nmove) {
+ if (nflushd_lines == 0) {
+ /*
+ * Allow backup past first
+ * line if nothing has been
+ * flushed yet.
+ */
+ for (; nmove < 0; nmove++) {
+ lnew = alloc_line();
+ l->l_prev = lnew;
+ lnew->l_next = l;
+ l = lines = lnew;
+ extra_lines++;
+ }
+ } else {
+ if (!warned++)
+ warnx(
+ _("warning: can't back up %s."), cur_line < 0 ?
+ _("past first line") : _("-- line already flushed"));
+ cur_line -= nmove;
+ }
+ }
+ } else {
+ /* may need to allocate here */
+ for (; nmove > 0 && l->l_next; nmove--)
+ l = l->l_next;
+ for (; nmove > 0; nmove--) {
+ lnew = alloc_line();
+ lnew->l_prev = l;
+ l->l_next = lnew;
+ l = lnew;
+ }
+ }
+ this_line = cur_line + adjust;
+ nmove = this_line - nflushd_lines;
+ if (nmove > 0
+ && (unsigned) nmove >= max_bufd_lines + BUFFER_MARGIN) {
+ nflushd_lines += nmove - max_bufd_lines;
+ flush_lines(nmove - max_bufd_lines);
+ }
+ }
+ /* grow line's buffer? */
+ if (l->l_line_len + 1 >= l->l_lsize) {
+ int need;
+
+ need = l->l_lsize ? l->l_lsize * 2 : 90;
+ l->l_line = xrealloc((void *) l->l_line,
+ (unsigned) need * sizeof(CHAR));
+ l->l_lsize = need;
+ }
+ c = &l->l_line[l->l_line_len++];
+ c->c_char = ch;
+ c->c_set = cur_set;
+ if (0 < cur_col)
+ c->c_column = cur_col;
+ else
+ c->c_column = 0;
+ c->c_width = wcwidth(ch);
+ /*
+ * If things are put in out of order, they will need sorting
+ * when it is flushed.
+ */
+ if (cur_col < l->l_max_col)
+ l->l_needs_sort = 1;
+ else
+ l->l_max_col = cur_col;
+ if (c->c_width > 0)
+ cur_col += c->c_width;
+ }
+ /* goto the last line that had a character on it */
+ for (; l->l_next; l = l->l_next)
+ this_line++;
+ if (max_line == 0 && cur_col == 0)
+ return EXIT_SUCCESS; /* no lines, so just exit */
+ flush_lines(this_line - nflushd_lines + extra_lines + 1);
+
+ /* make sure we leave things in a sane state */
+ if (last_set != CS_NORMAL)
+ PUTC('\017');
+
+ /* flush out the last few blank lines */
+ nblank_lines = max_line - this_line;
+ if (max_line & 1)
+ nblank_lines++;
+ else if (!nblank_lines)
+ /* missing a \n on the last line? */
+ nblank_lines = 2;
+ flush_blanks();
+ return ret;
+}
+
+void flush_lines(int nflush)
+{
+ LINE *l;
+
+ while (--nflush >= 0) {
+ l = lines;
+ lines = l->l_next;
+ if (l->l_line) {
+ flush_blanks();
+ flush_line(l);
+ }
+ nblank_lines++;
+ free((void *)l->l_line);
+ free_line(l);
+ }
+ if (lines)
+ lines->l_prev = NULL;
+}
+
+/*
+ * Print a number of newline/half newlines. If fine flag is set, nblank_lines
+ * is the number of half line feeds, otherwise it is the number of whole line
+ * feeds.
+ */
+void flush_blanks(void)
+{
+ int half, i, nb;
+
+ half = 0;
+ nb = nblank_lines;
+ if (nb & 1) {
+ if (fine)
+ half = 1;
+ else
+ nb++;
+ }
+ nb /= 2;
+ for (i = nb; --i >= 0;)
+ PUTC('\n');
+ if (half) {
+ PUTC('\033');
+ PUTC('9');
+ if (!nb)
+ PUTC('\r');
+ }
+ nblank_lines = 0;
+}
+
+/*
+ * Write a line to stdout taking care of space to tab conversion (-h flag)
+ * and character set shifts.
+ */
+void flush_line(LINE *l)
+{
+ CHAR *c, *endc;
+ int nchars, last_col, this_col;
+
+ last_col = 0;
+ nchars = l->l_line_len;
+
+ if (l->l_needs_sort) {
+ static CHAR *sorted = NULL;
+ static int count_size = 0, *count = NULL, sorted_size = 0;
+ int i, tot;
+
+ /*
+ * Do an O(n) sort on l->l_line by column being careful to
+ * preserve the order of characters in the same column.
+ */
+ if (l->l_lsize > sorted_size) {
+ sorted_size = l->l_lsize;
+ sorted = xrealloc((void *)sorted,
+ (unsigned)sizeof(CHAR) * sorted_size);
+ }
+ if (l->l_max_col >= count_size) {
+ count_size = l->l_max_col + 1;
+ count = (int *)xrealloc((void *)count,
+ (unsigned)sizeof(int) * count_size);
+ }
+ memset(count, 0, sizeof(int) * l->l_max_col + 1);
+ for (i = nchars, c = l->l_line; c && --i >= 0; c++)
+ count[c->c_column]++;
+
+ /*
+ * calculate running total (shifted down by 1) to use as
+ * indices into new line.
+ */
+ for (tot = 0, i = 0; i <= l->l_max_col; i++) {
+ int save = count[i];
+ count[i] = tot;
+ tot += save;
+ }
+
+ for (i = nchars, c = l->l_line; --i >= 0; c++)
+ sorted[count[c->c_column]++] = *c;
+ c = sorted;
+ } else
+ c = l->l_line;
+ while (nchars > 0) {
+ this_col = c->c_column;
+ endc = c;
+ do {
+ ++endc;
+ } while (--nchars > 0 && this_col == endc->c_column);
+
+ /* if -b only print last character */
+ if (no_backspaces) {
+ c = endc - 1;
+ if (nchars > 0 &&
+ this_col + c->c_width > endc->c_column)
+ continue;
+ }
+
+ if (this_col > last_col) {
+ int nspace = this_col - last_col;
+
+ if (compress_spaces && nspace > 1) {
+ int ntabs;
+
+ ntabs = this_col / 8 - last_col / 8;
+ if (ntabs > 0) {
+ nspace = this_col & 7;
+ while (--ntabs >= 0)
+ PUTC('\t');
+ }
+ }
+ while (--nspace >= 0)
+ PUTC(' ');
+ last_col = this_col;
+ }
+
+ for (;;) {
+ if (c->c_set != last_set) {
+ switch (c->c_set) {
+ case CS_NORMAL:
+ PUTC('\017');
+ break;
+ case CS_ALTERNATE:
+ PUTC('\016');
+ }
+ last_set = c->c_set;
+ }
+ PUTC(c->c_char);
+ if ((c + 1) < endc) {
+ int i;
+ for (i=0; i < c->c_width; i++)
+ PUTC('\b');
+ }
+ if (++c >= endc)
+ break;
+ }
+ last_col += (c - 1)->c_width;
+ }
+}
+
+#define NALLOC 64
+
+static LINE *line_freelist;
+
+LINE *
+alloc_line(void)
+{
+ LINE *l;
+ int i;
+
+ if (!line_freelist) {
+ l = xmalloc(sizeof(LINE) * NALLOC);
+ line_freelist = l;
+ for (i = 1; i < NALLOC; i++, l++)
+ l->l_next = l + 1;
+ l->l_next = NULL;
+ }
+ l = line_freelist;
+ line_freelist = l->l_next;
+
+ memset(l, 0, sizeof(LINE));
+ return l;
+}
+
+void free_line(LINE *l)
+{
+ l->l_next = line_freelist;
+ line_freelist = l;
+}
diff --git a/text-utils/colcrt.1 b/text-utils/colcrt.1
new file mode 100644
index 0000000..46c612b
--- /dev/null
+++ b/text-utils/colcrt.1
@@ -0,0 +1,105 @@
+.\" Copyright (c) 1980, 1990, 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)colcrt.1 8.1 (Berkeley) 6/30/93
+.\"
+.TH COLCRT "1" "September 2011" "util-linux" "User Commands"
+.SH NAME
+colcrt \- filter nroff output for CRT previewing
+.SH SYNOPSIS
+.B colcrt
+[options]
+.RI [ file ...]
+.SH DESCRIPTION
+.B colcrt
+provides virtual half-line and reverse line feed sequences for terminals
+without such capability, and on which overstriking is destructive.
+Half-line characters and underlining (changed to dashing `\-') are placed on
+new lines in between the normal output lines.
+.SH OPTIONS
+.TP
+\fB\-\fR, \fB\-\-no\-underlining\fR
+Suppress all underlining. This option is especially useful for previewing
+.I allboxed
+tables from
+.BR tbl (1).
+.TP
+\fB\-2\fR, \fB\-\-half\-lines\fR
+Causes all half-lines to be printed, effectively double spacing the output.
+Normally, a minimal space output format is used which will suppress empty
+lines. The program never suppresses two consecutive empty lines, however.
+The
+.B \-2
+option is useful for sending output to the line printer when the output
+contains superscripts and subscripts which would otherwise be invisible.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH HISTORY
+The
+.B colcrt
+command appeared in 3.0BSD.
+.SH BUGS
+Should fold underlines onto blanks even with the
+.B '\-'
+option so that a true underline character would show.
+.PP
+Can't back up more than 102 lines.
+.PP
+General overstriking is lost; as a special case '|' overstruck with '\-' or
+underline becomes '+'.
+.PP
+Lines are trimmed to 132 characters.
+.PP
+Some provision should be made for processing superscripts and subscripts in
+documents which are already double-spaced.
+.SH EXAMPLES
+A typical use of
+.B colcrt
+would be:
+.PP
+.RS
+.B tbl exum2.n \&| nroff \-ms \&| colcrt \- \&| more
+.RE
+.SH SEE ALSO
+.BR col (1),
+.BR more (1),
+.BR nroff (1),
+.BR troff (1),
+.BR ul (1)
+.SH AVAILABILITY
+The colcrt command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/text-utils/colcrt.c b/text-utils/colcrt.c
new file mode 100644
index 0000000..113e3d0
--- /dev/null
+++ b/text-utils/colcrt.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2016 Sami Kerola <kerolasa@iki.fi>
+ * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
+ *
+ * Copyright (c) 1980, 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+/*
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * added Native Language Support
+ * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
+ * modified to work correctly in multi-byte locales
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include "nls.h"
+#include "c.h"
+#include "widechar.h"
+#include "closestream.h"
+
+/*
+ * colcrt - replaces col for crts with new nroff esp. when using tbl.
+ * Bill Joy UCB July 14, 1977
+ *
+ * This filter uses the up and down sequences generated by the new
+ * nroff when used with tbl and by \u \d and \r.
+ * General overstriking doesn't work correctly.
+ * Underlining is split onto multiple lines, etc.
+ *
+ * Option - suppresses all underlining.
+ * Option -2 forces printing of all half lines.
+ */
+
+enum { OUTPUT_COLS = 132 };
+
+struct colcrt_control {
+ FILE *f;
+ wchar_t line[OUTPUT_COLS + 1];
+ wchar_t line_under[OUTPUT_COLS + 1];
+ unsigned int print_nl:1,
+ need_line_under:1,
+ no_underlining:1,
+ half_lines:1;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Filter nroff output for CRT previewing.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -, --no-underlining suppress all underlining\n"), out);
+ fputs(_(" -2, --half-lines print all half-lines\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(25));
+
+ printf(USAGE_MAN_TAIL("colcrt(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static void trim_trailing_spaces(wchar_t *s)
+{
+ size_t size;
+ wchar_t *end;
+
+ size = wcslen(s);
+ if (!size)
+ return;
+ end = s + size - 1;
+ while (s <= end && iswspace(*end))
+ end--;
+ *(end + 1) = L'\0';
+}
+
+
+static void output_lines(struct colcrt_control *ctl, int col)
+{
+ /* first line */
+ trim_trailing_spaces(ctl->line);
+ fputws(ctl->line, stdout);
+
+ if (ctl->print_nl)
+ fputwc(L'\n', stdout);
+ if (!ctl->half_lines && !ctl->no_underlining)
+ ctl->print_nl = 0;
+
+ wmemset(ctl->line, L'\0', OUTPUT_COLS);
+
+ /* second line */
+ if (ctl->need_line_under) {
+ ctl->need_line_under = 0;
+ ctl->line_under[col] = L'\0';
+ trim_trailing_spaces(ctl->line_under);
+ fputws(ctl->line_under, stdout);
+ fputwc(L'\n', stdout);
+ wmemset(ctl->line_under, L' ', OUTPUT_COLS);
+
+ } else if (ctl->half_lines && 0 < col)
+ fputwc(L'\n', stdout);
+}
+
+static int rubchars(struct colcrt_control *ctl, int col, int n)
+{
+ while (0 < n && 0 < col) {
+ ctl->line[col] = L'\0';
+ ctl->line_under[col] = L' ';
+ n--;
+ col--;
+ }
+ return col;
+}
+
+static void colcrt(struct colcrt_control *ctl)
+{
+ int col;
+ wint_t c = 0;
+ long old_pos;
+
+ ctl->print_nl = 1;
+ if (ctl->half_lines)
+ fputwc(L'\n', stdout);
+
+ for (col = 0; /* nothing */; col++) {
+ if (OUTPUT_COLS - 1 < col) {
+ output_lines(ctl, col);
+ errno = 0;
+ old_pos = ftell(ctl->f);
+
+ while (getwc(ctl->f) != L'\n') {
+ long new_pos;
+
+ if (ferror(ctl->f) || feof(ctl->f))
+ return;
+ new_pos = ftell(ctl->f);
+ if (old_pos == new_pos) {
+ if (fseek(ctl->f, 1, SEEK_CUR) < 1)
+ return;
+ } else
+ old_pos = new_pos;
+ }
+ col = -1;
+ continue;
+ }
+ c = getwc(ctl->f);
+ switch (c) {
+ case 033: /* ESC */
+ c = getwc(ctl->f);
+ if (c == L'8') {
+ col = rubchars(ctl, col, 1);
+ continue;
+ }
+ if (c == L'7') {
+ col = rubchars(ctl, col, 2);
+ continue;
+ }
+ continue;
+ case WEOF:
+ ctl->print_nl = 0;
+ output_lines(ctl, col);
+ return;
+ case L'\n':
+ output_lines(ctl, col);
+ col = -1;
+ continue;
+ case L'\t':
+ for (/* nothing */; col % 8 && col < OUTPUT_COLS; col++) {
+ ctl->line[col] = L' ';
+ }
+ col--;
+ continue;
+ case L'_':
+ ctl->line[col] = L' ';
+ if (!ctl->no_underlining) {
+ ctl->need_line_under = 1;
+ ctl->line_under[col] = L'-';
+ }
+ continue;
+ default:
+ if (!iswprint(c)) {
+ col--;
+ continue;
+ }
+ ctl->print_nl = 1;
+ ctl->line[col] = c;
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct colcrt_control ctl = { NULL };
+ int opt;
+ enum { NO_UL_OPTION = CHAR_MAX + 1 };
+
+ static const struct option longopts[] = {
+ {"no-underlining", no_argument, NULL, NO_UL_OPTION},
+ {"half-lines", no_argument, NULL, '2'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ /* Take care of lonely hyphen option. */
+ for (opt = 0; opt < argc; opt++) {
+ if (argv[opt][0] == '-' && argv[opt][1] == '\0') {
+ ctl.no_underlining = 1;
+ argc--;
+ memmove(argv + opt, argv + opt + 1,
+ sizeof(char *) * (argc - opt));
+ opt--;
+ }
+ }
+
+ while ((opt = getopt_long(argc, argv, "2Vh", longopts, NULL)) != -1) {
+ switch (opt) {
+ case NO_UL_OPTION:
+ ctl.no_underlining = 1;
+ break;
+ case '2':
+ ctl.half_lines = 1;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ do {
+ wmemset(ctl.line, L'\0', OUTPUT_COLS);
+ wmemset(ctl.line_under, L' ', OUTPUT_COLS);
+
+ if (argc > 0) {
+ if (!(ctl.f = fopen(*argv, "r")))
+ err(EXIT_FAILURE, _("cannot open %s"), *argv);
+ argc--;
+ argv++;
+ } else
+ ctl.f = stdin;
+
+ colcrt(&ctl);
+ if (ctl.f != stdin)
+ fclose(ctl.f);
+ } while (argc > 0);
+
+ return EXIT_SUCCESS;
+}
diff --git a/text-utils/colrm.1 b/text-utils/colrm.1
new file mode 100644
index 0000000..979137f
--- /dev/null
+++ b/text-utils/colrm.1
@@ -0,0 +1,75 @@
+.\" Copyright (c) 1980, 1990 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)colrm.1 6.6 (Berkeley) 3/14/91
+.\"
+.TH COLRM "1" "September 2011" "util-linux" "User Commands"
+.SH NAME
+colrm \- remove columns from a file
+.SH SYNOPSIS
+.B colrm
+.RI [ first \ [ last ]]
+.SH DESCRIPTION
+.B colrm
+removes selected columns from a file. Input is taken from standard input.
+Output is sent to standard output.
+.PP
+If called with one parameter the columns of each line will be removed
+starting with the specified
+.I first
+column. If called with two parameters the columns from the
+.I first
+column to the
+.I last
+column will be removed.
+.PP
+Column numbering starts with column 1.
+.SH OPTIONS
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH HISTORY
+The
+.B colrm
+command appeared in 3.0BSD.
+.SH SEE ALSO
+.BR awk (1p),
+.BR column (1),
+.BR expand (1),
+.BR paste (1)
+.SH AVAILABILITY
+The colrm command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/text-utils/colrm.c b/text-utils/colrm.c
new file mode 100644
index 0000000..f9dfd59
--- /dev/null
+++ b/text-utils/colrm.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 1980 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+/*
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * added Native Language Support
+ * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
+ * modified to work correctly in multi-byte locales
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <unistd.h>
+
+#include "nls.h"
+#include "strutils.h"
+#include "c.h"
+#include "widechar.h"
+#include "closestream.h"
+
+/*
+COLRM removes unwanted columns from a file
+ Jeff Schriebman UC Berkeley 11-74
+*/
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fprintf(out, _("\nUsage:\n"
+ " %s [startcol [endcol]]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Filter out the specified columns.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ fprintf(out, _("%s reads from standard input and writes to standard output\n\n"),
+ program_invocation_short_name);
+ printf(USAGE_MAN_TAIL("colrm(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static int process_input(unsigned long first, unsigned long last)
+{
+ unsigned long ct = 0;
+ wint_t c;
+ unsigned long i;
+ int w;
+ int padding;
+
+ for (;;) {
+ c = getwc(stdin);
+ if (c == WEOF)
+ return 0;
+ if (c == '\t')
+ w = ((ct + 8) & ~7) - ct;
+ else if (c == '\b')
+ w = (ct ? ct - 1 : 0) - ct;
+ else {
+ w = wcwidth(c);
+ if (w < 0)
+ w = 0;
+ }
+ ct += w;
+ if (c == '\n') {
+ putwc(c, stdout);
+ ct = 0;
+ continue;
+
+ }
+ if (!first || ct < first) {
+ putwc(c, stdout);
+ continue;
+ }
+ break;
+ }
+
+ for (i = ct - w + 1; i < first; i++)
+ putwc(' ', stdout);
+
+ /* Loop getting rid of characters */
+ while (!last || ct < last) {
+ c = getwc(stdin);
+ if (c == WEOF)
+ return 0;
+ if (c == '\n') {
+ putwc(c, stdout);
+ return 1;
+ }
+ if (c == '\t')
+ ct = (ct + 8) & ~7;
+ else if (c == '\b')
+ ct = ct ? ct - 1 : 0;
+ else {
+ w = wcwidth(c);
+ if (w < 0)
+ w = 0;
+ ct += w;
+ }
+ }
+
+ padding = 0;
+
+ /* Output last of the line */
+ for (;;) {
+ c = getwc(stdin);
+ if (c == WEOF)
+ break;
+ if (c == '\n') {
+ putwc(c, stdout);
+ return 1;
+ }
+ if (padding == 0 && last < ct) {
+ for (i = last; i < ct; i++)
+ putwc(' ', stdout);
+ padding = 1;
+ }
+ putwc(c, stdout);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned long first = 0, last = 0;
+ int opt;
+
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((opt =
+ getopt_long(argc, argv, "bfhl:pxVH", longopts,
+ NULL)) != -1)
+ switch (opt) {
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (argc > 1)
+ first = strtoul_or_err(*++argv, _("first argument"));
+ if (argc > 2)
+ last = strtoul_or_err(*++argv, _("second argument"));
+
+ while (process_input(first, last))
+ ;
+
+ fflush(stdout);
+ return EXIT_SUCCESS;
+}
diff --git a/text-utils/column.1 b/text-utils/column.1
new file mode 100644
index 0000000..1869695
--- /dev/null
+++ b/text-utils/column.1
@@ -0,0 +1,186 @@
+.\" Copyright (c) 1989, 1990, 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)column.1 8.1 (Berkeley) 6/6/93
+.\"
+.TH COLUMN 1 "February 2019" "util-linux" "User Commands"
+.SH NAME
+column \- columnate lists
+.SH SYNOPSIS
+.BR column " [options]"
+.RI [ file ...]
+.SH DESCRIPTION
+The
+.B column
+utility formats its input into multiple columns. The util support three modes:
+.TP
+.B columns are filled before rows
+This is the default mode (required by backward compatibility).
+.TP
+.B rows are filled before columns
+This mode is enabled by option \fB\-x, \-\-fillrows\fP
+.TP
+.B table
+Determine the number of columns the input contains and create a table. This
+mode is enabled by option \fB\-t, \-\-table\fP and columns formatting is
+possible to modify by \fB\-\-table-*\fP options. Use this mode if not sure.
+.PP
+Input is taken from \fIfile\fR, or otherwise from standard input. Empty lines
+are ignored and all invalid multibyte sequences are encoded by \\x<hex> convention.
+.SH OPTIONS
+The argument \fIcolumns\fP for \fB\-\-table-*\fP options is comma separated
+list of the column names as defined by \fB\-\-table-columns\fP or it's column
+number in order as specified by input. It's possible to mix names and numbers.
+.IP "\fB\-J, \-\-json\fP"
+Use JSON output format to print the table, the option
+\fB\-\-table\-columns\fP is required and the option \fB\-\-table\-name\fP is recommended.
+.IP "\fB\-c, \-\-output\-width\fP \fIwidth\fP"
+Output is formatted to a width specified as number of characters. The original
+name of this option is \-\-columns; this name is deprecated since v2.30. Note that input
+longer than \fIwidth\fP is not truncated by default.
+.IP "\fB\-d, \-\-table\-noheadings\fP"
+Do not print header.
+This option allows the use of logical column names on the command line,
+but keeps the header hidden when printing the table.
+.IP "\fB\-o, \-\-output\-separator\fP \fIstring\fP"
+Specify the columns delimiter for table output (default is two spaces).
+.IP "\fB\-s, \-\-separator\fP \fIseparators\fP"
+Specify the possible input item delimiters (default is whitespace).
+.IP "\fB\-t, \-\-table\fP"
+Determine the number of columns the input contains and create a table.
+Columns are delimited with whitespace, by default, or with the characters
+supplied using the \fB\-\-output\-separator\fP option.
+Table output is useful for pretty-printing.
+.IP "\fB\-N, \-\-table-columns\fP \fInames\fP"
+Specify the columns names by comma separated list of names. The names are used
+for the table header or to address column in option arguments.
+.IP "\fB\-R, \-\-table-right\fP \fIcolumns\fP"
+Right align text in the specified columns.
+.IP "\fB\-T, \-\-table-truncate\fP \fIcolumns\fP"
+Specify columns where text can be truncated when necessary, otherwise
+very long table entries may be printed on multiple lines.
+.IP "\fB\-E, \-\-table-noextreme\fP \fIcolumns\fP"
+Specify columns where is possible to ignore unusually long (longer than
+average) cells when calculate column width. The option has impact to the width
+calculation and table formatting, but the printed text is not affected.
+
+The option is used for the last visible column by default.
+
+.IP "\fB\-e, \-\-table\-header\-repeat\fP"
+Print header line for each page.
+.IP "\fB\-W, \-\-table-wrap\fP \fIcolumns\fP"
+Specify columns where is possible to use multi-line cell for long text when
+necessary.
+.IP "\fB\-H, \-\-table-hide\fP \fIcolumns\fP"
+Don't print specified columns. The special placeholder '\-' may be used to
+hide all unnamed columns (see \-\-table-columns).
+.IP "\fB\-O, \-\-table-order\fP \fIcolumns\fP"
+Specify columns order on output.
+.IP "\fB\-n, \-\-table-name\fP \fIname\fP"
+Specify the table name used for JSON output. The default is "table".
+.IP "\fB\-L, \-\-table\-empty\-lines\fP"
+Insert empty line to the table for each empty line on input. The default
+is ignore empty lines at all.
+.IP "\fB\-r, \-\-tree\fP \fIcolumn\fP"
+Specify column to use tree-like output. Note that the circular dependencies and
+other anomalies in child and parent relation are silently ignored.
+.IP "\fB\-i, \-\-tree\-id\fP \fIcolumn\fP"
+Specify column with line ID to create child-parent relation.
+.IP "\fB\-p, \-\-tree\-parent\fP \fIcolumn\fP"
+Specify column with parent ID to create child-parent relation.
+.IP "\fB\-x, \-\-fillrows\fP"
+Fill rows before filling columns.
+.IP "\fB\-V\fR, \fB\-\-version\fR"
+Display version information and exit.
+.IP "\fB\-h, \-\-help\fP"
+Display help text and exit.
+.SH ENVIRONMENT
+The environment variable \fBCOLUMNS\fR is used to determine the size of
+the screen if no other information is available.
+.SH HISTORY
+The column command appeared in 4.3BSD-Reno.
+.SH BUGS
+Version 2.23 changed the
+.B \-s
+option to be non-greedy, for example:
+.PP
+.EX
+\fBprintf "a:b:c\\n1::3\\n" | column \-t \-s ':'\fR
+.EE
+.PP
+Old output:
+.EX
+a b c
+1 3
+.EE
+.PP
+New output (since util-linux 2.23):
+.EX
+a b c
+1 3
+.EE
+.PP
+Historical versions of this tool indicated that "rows are filled before
+columns" by default, and that the
+.B \-x
+option reverses this. This wording did not reflect the actual behavior, and it
+has since been corrected (see above). Other implementations of
+.B column
+may continue to use the older documentation, but the behavior should be
+identical in any case.
+.SH EXAMPLES
+Print fstab with header line and align number to the right:
+.EX
+\fBsed 's/#.*//' /etc/fstab | column \-\-table \-\-table-columns SOURCE,TARGET,TYPE,OPTIONS,PASS,FREQ \-\-table-right PASS,FREQ\fR
+.EE
+.PP
+Print fstab and hide unnamed columns:
+.EX
+\fBsed 's/#.*//' /etc/fstab | column \-\-table \-\-table-columns SOURCE,TARGET,TYPE \-\-table-hide \-\fR
+.EE
+.PP
+Print a tree:
+.EX
+\fBecho \-e '1 0 A\\n2 1 AA\\n3 1 AB\\n4 2 AAA\\n5 2 AAB' | column \-\-tree-id 1 \-\-tree-parent 2 \-\-tree 3\fR
+1 0 A
+2 1 |-AA
+4 2 | |-AAA
+5 2 | `-AAB
+3 1 `-AB
+.EE
+.SH SEE ALSO
+.BR colrm (1),
+.BR ls (1),
+.BR paste (1),
+.BR sort (1)
+.SH AVAILABILITY
+The column command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/text-utils/column.c b/text-utils/column.c
new file mode 100644
index 0000000..33c7f1f
--- /dev/null
+++ b/text-utils/column.c
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ *
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include "nls.h"
+#include "c.h"
+#include "widechar.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "closestream.h"
+#include "ttyutils.h"
+#include "strv.h"
+#include "optutils.h"
+#include "mbsalign.h"
+
+#include "libsmartcols.h"
+
+#define TABCHAR_CELLS 8
+
+enum {
+ COLUMN_MODE_FILLCOLS = 0,
+ COLUMN_MODE_FILLROWS,
+ COLUMN_MODE_TABLE,
+ COLUMN_MODE_SIMPLE
+};
+
+struct column_control {
+ int mode; /* COLUMN_MODE_* */
+ size_t termwidth;
+
+ struct libscols_table *tab;
+
+ char **tab_colnames; /* array with column names */
+ const char *tab_name; /* table name */
+ const char *tab_order; /* --table-order */
+
+ const char *tab_colright; /* --table-right */
+ const char *tab_coltrunc; /* --table-trunc */
+ const char *tab_colnoextrem; /* --table-noextreme */
+ const char *tab_colwrap; /* --table-wrap */
+ const char *tab_colhide; /* --table-hide */
+
+ const char *tree;
+ const char *tree_id;
+ const char *tree_parent;
+
+ wchar_t *input_separator;
+ const char *output_separator;
+
+ wchar_t **ents; /* input entries */
+ size_t nents; /* number of entries */
+ size_t maxlength; /* longest input record (line) */
+
+ unsigned int greedy :1,
+ json :1,
+ header_repeat :1,
+ tab_empty_lines :1, /* --table-empty-lines */
+ tab_noheadings :1;
+};
+
+static size_t width(const wchar_t *str)
+{
+ size_t width = 0;
+
+ for (; *str != '\0'; str++) {
+#ifdef HAVE_WIDECHAR
+ int x = wcwidth(*str); /* don't use wcswidth(), need to ignore non-printable */
+ if (x > 0)
+ width += x;
+#else
+ if (isprint(*str))
+ width++;
+#endif
+ }
+ return width;
+}
+
+static wchar_t *mbs_to_wcs(const char *s)
+{
+#ifdef HAVE_WIDECHAR
+ ssize_t n;
+ wchar_t *wcs;
+
+ n = mbstowcs((wchar_t *)0, s, 0);
+ if (n < 0)
+ return NULL;
+ wcs = xcalloc((n + 1) * sizeof(wchar_t), 1);
+ n = mbstowcs(wcs, s, n + 1);
+ if (n < 0) {
+ free(wcs);
+ return NULL;
+ }
+ return wcs;
+#else
+ return xstrdup(s);
+#endif
+}
+
+static char *wcs_to_mbs(const wchar_t *s)
+{
+#ifdef HAVE_WIDECHAR
+ size_t n;
+ char *str;
+
+ n = wcstombs(NULL, s, 0);
+ if (n == (size_t) -1)
+ return NULL;
+
+ str = xcalloc(n + 1, 1);
+ if (wcstombs(str, s, n) == (size_t) -1) {
+ free(str);
+ return NULL;
+ }
+ return str;
+#else
+ return xstrdup(s);
+#endif
+}
+
+static wchar_t *local_wcstok(struct column_control const *const ctl, wchar_t *p,
+ wchar_t **state)
+{
+ wchar_t *result = NULL;
+
+ if (ctl->greedy)
+#ifdef HAVE_WIDECHAR
+ return wcstok(p, ctl->input_separator, state);
+#else
+ return strtok_r(p, ctl->input_separator, state);
+#endif
+ if (!p) {
+ if (!*state)
+ return NULL;
+ p = *state;
+ }
+ result = p;
+#ifdef HAVE_WIDECHAR
+ p = wcspbrk(result, ctl->input_separator);
+#else
+ p = strpbrk(result, ctl->input_separator);
+#endif
+ if (!p)
+ *state = NULL;
+ else {
+ *p = '\0';
+ *state = p + 1;
+ }
+ return result;
+}
+
+static char **split_or_error(const char *str, const char *errmsg)
+{
+ char **res = strv_split(str, ",");
+ if (!res) {
+ if (errno == ENOMEM)
+ err_oom();
+ errx(EXIT_FAILURE, "%s: '%s'", errmsg, str);
+ }
+ return res;
+}
+
+static void init_table(struct column_control *ctl)
+{
+ scols_init_debug(0);
+
+ ctl->tab = scols_new_table();
+ if (!ctl->tab)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ scols_table_set_column_separator(ctl->tab, ctl->output_separator);
+ if (ctl->json) {
+ scols_table_enable_json(ctl->tab, 1);
+ scols_table_set_name(ctl->tab, ctl->tab_name ? : "table");
+ } else
+ scols_table_enable_noencoding(ctl->tab, 1);
+
+ if (ctl->tab_colnames) {
+ char **name;
+
+ STRV_FOREACH(name, ctl->tab_colnames)
+ scols_table_new_column(ctl->tab, *name, 0, 0);
+ if (ctl->header_repeat)
+ scols_table_enable_header_repeat(ctl->tab, 1);
+ scols_table_enable_noheadings(ctl->tab, !!ctl->tab_noheadings);
+ } else
+ scols_table_enable_noheadings(ctl->tab, 1);
+}
+
+static struct libscols_column *string_to_column(struct column_control *ctl, const char *str)
+{
+ uint32_t colnum = 0;
+
+ if (isdigit_string(str))
+ colnum = strtou32_or_err(str, _("failed to parse column")) - 1;
+ else {
+ char **name;
+
+ STRV_FOREACH(name, ctl->tab_colnames) {
+ if (strcasecmp(*name, str) == 0)
+ break;
+ colnum++;
+ }
+ if (!name || !*name)
+ errx(EXIT_FAILURE, _("undefined column name '%s'"), str);
+ }
+
+ return scols_table_get_column(ctl->tab, colnum);
+}
+
+static struct libscols_column *get_last_visible_column(struct column_control *ctl)
+{
+ struct libscols_iter *itr;
+ struct libscols_column *cl, *last = NULL;
+
+ itr = scols_new_iter(SCOLS_ITER_FORWARD);
+ if (!itr)
+ err_oom();
+
+ while (scols_table_next_column(ctl->tab, itr, &cl) == 0) {
+ if (scols_column_get_flags(cl) & SCOLS_FL_HIDDEN)
+ continue;
+ last = cl;
+ }
+
+ scols_free_iter(itr);
+ return last;
+}
+
+static int column_set_flag(struct libscols_column *cl, int fl)
+{
+ int cur = scols_column_get_flags(cl);
+
+ return scols_column_set_flags(cl, cur | fl);
+}
+
+static void apply_columnflag_from_list(struct column_control *ctl, const char *list,
+ int flag, const char *errmsg)
+{
+ char **all = split_or_error(list, errmsg);
+ char **one;
+ int unnamed = 0;
+
+ STRV_FOREACH(one, all) {
+ struct libscols_column *cl;
+
+ if (flag == SCOLS_FL_HIDDEN && strcmp(*one, "-") == 0) {
+ unnamed = 1;
+ continue;
+ }
+ cl = string_to_column(ctl, *one);
+ if (cl)
+ column_set_flag(cl, flag);
+ }
+ strv_free(all);
+
+ /* apply flag to all columns without name */
+ if (unnamed) {
+ struct libscols_iter *itr;
+ struct libscols_column *cl;
+
+ itr = scols_new_iter(SCOLS_ITER_FORWARD);
+ if (!itr)
+ err_oom();
+
+ while (scols_table_next_column(ctl->tab, itr, &cl) == 0) {
+ struct libscols_cell *ce = scols_column_get_header(cl);
+
+ if (ce == NULL || scols_cell_get_data(ce) == NULL)
+ column_set_flag(cl, flag);
+ }
+ scols_free_iter(itr);
+ }
+}
+
+static void reorder_table(struct column_control *ctl)
+{
+ struct libscols_column **wanted, *last = NULL;
+ size_t i, count = 0;
+ size_t ncols = scols_table_get_ncols(ctl->tab);
+ char **order = split_or_error(ctl->tab_order, _("failed to parse --table-order list"));
+ char **one;
+
+ wanted = xcalloc(ncols, sizeof(struct libscols_column *));
+
+ STRV_FOREACH(one, order) {
+ struct libscols_column *cl = string_to_column(ctl, *one);
+ if (cl)
+ wanted[count++] = cl;
+ }
+
+ for (i = 0; i < count; i++) {
+ scols_table_move_column(ctl->tab, last, wanted[i]);
+ last = wanted[i];
+ }
+
+ free(wanted);
+ strv_free(order);
+}
+
+static void create_tree(struct column_control *ctl)
+{
+ struct libscols_column *cl_tree = string_to_column(ctl, ctl->tree);
+ struct libscols_column *cl_p = string_to_column(ctl, ctl->tree_parent);
+ struct libscols_column *cl_i = string_to_column(ctl, ctl->tree_id);
+ struct libscols_iter *itr_p, *itr_i;
+ struct libscols_line *ln_i;
+
+ if (!cl_p || !cl_i || !cl_tree)
+ return; /* silently ignore the tree request */
+
+ column_set_flag(cl_tree, SCOLS_FL_TREE);
+
+ itr_p = scols_new_iter(SCOLS_ITER_FORWARD);
+ itr_i = scols_new_iter(SCOLS_ITER_FORWARD);
+ if (!itr_p || !itr_i)
+ err_oom();
+
+ /* scan all lines for ID */
+ while (scols_table_next_line(ctl->tab, itr_i, &ln_i) == 0) {
+ struct libscols_line *ln;
+ struct libscols_cell *ce = scols_line_get_column_cell(ln_i, cl_i);
+ const char *id = ce ? scols_cell_get_data(ce) : NULL;
+
+ if (!id)
+ continue;
+
+ /* see if the ID is somewhere used in parent column */
+ scols_reset_iter(itr_p, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(ctl->tab, itr_p, &ln) == 0) {
+ const char *parent;
+
+ ce = scols_line_get_column_cell(ln, cl_p);
+ parent = ce ? scols_cell_get_data(ce) : NULL;
+
+ if (!parent)
+ continue;
+ if (strcmp(id, parent) != 0)
+ continue;
+ if (scols_line_is_ancestor(ln, ln_i))
+ continue;
+ scols_line_add_child(ln_i, ln);
+ }
+ }
+
+ scols_free_iter(itr_p);
+ scols_free_iter(itr_i);
+}
+
+static void modify_table(struct column_control *ctl)
+{
+ scols_table_set_termwidth(ctl->tab, ctl->termwidth);
+ scols_table_set_termforce(ctl->tab, SCOLS_TERMFORCE_ALWAYS);
+
+ if (ctl->tab_colright)
+ apply_columnflag_from_list(ctl, ctl->tab_colright,
+ SCOLS_FL_RIGHT, _("failed to parse --table-right list"));
+
+ if (ctl->tab_coltrunc)
+ apply_columnflag_from_list(ctl, ctl->tab_coltrunc,
+ SCOLS_FL_TRUNC , _("failed to parse --table-trunc list"));
+
+ if (ctl->tab_colnoextrem)
+ apply_columnflag_from_list(ctl, ctl->tab_colnoextrem,
+ SCOLS_FL_NOEXTREMES , _("failed to parse --table-noextreme list"));
+
+ if (ctl->tab_colwrap)
+ apply_columnflag_from_list(ctl, ctl->tab_colwrap,
+ SCOLS_FL_WRAP , _("failed to parse --table-wrap list"));
+
+ if (ctl->tab_colhide)
+ apply_columnflag_from_list(ctl, ctl->tab_colhide,
+ SCOLS_FL_HIDDEN , _("failed to parse --table-hide list"));
+
+ if (!ctl->tab_colnoextrem) {
+ struct libscols_column *cl = get_last_visible_column(ctl);
+ if (cl)
+ column_set_flag(cl, SCOLS_FL_NOEXTREMES);
+ }
+
+ if (ctl->tree)
+ create_tree(ctl);
+
+ /* This must be the last step! */
+ if (ctl->tab_order)
+ reorder_table(ctl);
+}
+
+
+static int add_line_to_table(struct column_control *ctl, wchar_t *wcs)
+{
+ wchar_t *wcdata, *sv = NULL;
+ size_t n = 0;
+ struct libscols_line *ln = NULL;
+
+ if (!ctl->tab)
+ init_table(ctl);
+
+ while ((wcdata = local_wcstok(ctl, wcs, &sv))) {
+ char *data;
+
+ if (scols_table_get_ncols(ctl->tab) < n + 1) {
+ if (scols_table_is_json(ctl->tab))
+ errx(EXIT_FAILURE, _("line %zu: for JSON the name of the "
+ "column %zu is required"),
+ scols_table_get_nlines(ctl->tab) + 1,
+ n + 1);
+ scols_table_new_column(ctl->tab, NULL, 0, 0);
+ }
+ if (!ln) {
+ ln = scols_table_new_line(ctl->tab, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+ }
+
+ data = wcs_to_mbs(wcdata);
+ if (!data)
+ err(EXIT_FAILURE, _("failed to allocate output data"));
+ if (scols_line_refer_data(ln, n, data))
+ err(EXIT_FAILURE, _("failed to add output data"));
+ n++;
+ wcs = NULL;
+ }
+
+ return 0;
+}
+
+static int add_emptyline_to_table(struct column_control *ctl)
+{
+ if (!ctl->tab)
+ init_table(ctl);
+
+ if (!scols_table_new_line(ctl->tab, NULL))
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ return 0;
+}
+
+static int read_input(struct column_control *ctl, FILE *fp)
+{
+ char *buf = NULL;
+ size_t bufsz = 0;
+ size_t maxents = 0;
+ int rc = 0;
+
+ /* Read input */
+ do {
+ char *str, *p;
+ wchar_t *wcs = NULL;
+ size_t len;
+
+ if (getline(&buf, &bufsz, fp) < 0) {
+ if (feof(fp))
+ break;
+ err(EXIT_FAILURE, _("read failed"));
+ }
+ str = (char *) skip_space(buf);
+ if (str) {
+ p = strchr(str, '\n');
+ if (p)
+ *p = '\0';
+ }
+ if (!str || !*str) {
+ if (ctl->mode == COLUMN_MODE_TABLE && ctl->tab_empty_lines)
+ add_emptyline_to_table(ctl);
+ continue;
+ }
+
+ wcs = mbs_to_wcs(buf);
+ if (!wcs) {
+ /*
+ * Convert broken sequences to \x<hex> and continue.
+ */
+ size_t tmpsz = 0;
+ char *tmp = mbs_invalid_encode(buf, &tmpsz);
+
+ if (!tmp)
+ err(EXIT_FAILURE, _("read failed"));
+ wcs = mbs_to_wcs(tmp);
+ free(tmp);
+ }
+
+ switch (ctl->mode) {
+ case COLUMN_MODE_TABLE:
+ rc = add_line_to_table(ctl, wcs);
+ free(wcs);
+ break;
+
+ case COLUMN_MODE_FILLCOLS:
+ case COLUMN_MODE_FILLROWS:
+ if (ctl->nents <= maxents) {
+ maxents += 1000;
+ ctl->ents = xrealloc(ctl->ents,
+ maxents * sizeof(wchar_t *));
+ }
+ ctl->ents[ctl->nents] = wcs;
+ len = width(ctl->ents[ctl->nents]);
+ if (ctl->maxlength < len)
+ ctl->maxlength = len;
+ ctl->nents++;
+ break;
+ default:
+ free(wcs);
+ break;
+ }
+ } while (rc == 0);
+
+ return rc;
+}
+
+
+static void columnate_fillrows(struct column_control *ctl)
+{
+ size_t chcnt, col, cnt, endcol, numcols;
+ wchar_t **lp;
+
+ ctl->maxlength = (ctl->maxlength + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1);
+ numcols = ctl->termwidth / ctl->maxlength;
+ endcol = ctl->maxlength;
+ for (chcnt = col = 0, lp = ctl->ents; /* nothing */; ++lp) {
+ fputws(*lp, stdout);
+ chcnt += width(*lp);
+ if (!--ctl->nents)
+ break;
+ if (++col == numcols) {
+ chcnt = col = 0;
+ endcol = ctl->maxlength;
+ putwchar('\n');
+ } else {
+ while ((cnt = ((chcnt + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1))) <= endcol) {
+ putwchar('\t');
+ chcnt = cnt;
+ }
+ endcol += ctl->maxlength;
+ }
+ }
+ if (chcnt)
+ putwchar('\n');
+}
+
+static void columnate_fillcols(struct column_control *ctl)
+{
+ size_t base, chcnt, cnt, col, endcol, numcols, numrows, row;
+
+ ctl->maxlength = (ctl->maxlength + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1);
+ numcols = ctl->termwidth / ctl->maxlength;
+ if (!numcols)
+ numcols = 1;
+ numrows = ctl->nents / numcols;
+ if (ctl->nents % numcols)
+ ++numrows;
+
+ for (row = 0; row < numrows; ++row) {
+ endcol = ctl->maxlength;
+ for (base = row, chcnt = col = 0; col < numcols; ++col) {
+ fputws(ctl->ents[base], stdout);
+ chcnt += width(ctl->ents[base]);
+ if ((base += numrows) >= ctl->nents)
+ break;
+ while ((cnt = ((chcnt + TABCHAR_CELLS) & ~(TABCHAR_CELLS - 1))) <= endcol) {
+ putwchar('\t');
+ chcnt = cnt;
+ }
+ endcol += ctl->maxlength;
+ }
+ putwchar('\n');
+ }
+}
+
+static void simple_print(struct column_control *ctl)
+{
+ int cnt;
+ wchar_t **lp;
+
+ for (cnt = ctl->nents, lp = ctl->ents; cnt--; ++lp) {
+ fputws(*lp, stdout);
+ putwchar('\n');
+ }
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Columnate lists.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -t, --table create a table\n"), out);
+ fputs(_(" -n, --table-name <name> table name for JSON output\n"), out);
+ fputs(_(" -O, --table-order <columns> specify order of output columns\n"), out);
+ fputs(_(" -N, --table-columns <names> comma separated columns names\n"), out);
+ fputs(_(" -E, --table-noextreme <columns> don't count long text from the columns to column width\n"), out);
+ fputs(_(" -d, --table-noheadings don't print header\n"), out);
+ fputs(_(" -e, --table-header-repeat repeat header for each page\n"), out);
+ fputs(_(" -H, --table-hide <columns> don't print the columns\n"), out);
+ fputs(_(" -R, --table-right <columns> right align text in these columns\n"), out);
+ fputs(_(" -T, --table-truncate <columns> truncate text in the columns when necessary\n"), out);
+ fputs(_(" -W, --table-wrap <columns> wrap text in the columns when necessary\n"), out);
+ fputs(_(" -L, --table-empty-lines don't ignore empty lines\n"), out);
+ fputs(_(" -J, --json use JSON output format for table\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -r, --tree <column> column to use tree-like output for the table\n"), out);
+ fputs(_(" -i, --tree-id <column> line ID to specify child-parent relation\n"), out);
+ fputs(_(" -p, --tree-parent <column> parent to specify child-parent relation\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out);
+ fputs(_(" -o, --output-separator <string> columns separator for table output (default is two spaces)\n"), out);
+ fputs(_(" -s, --separator <string> possible table delimiters\n"), out);
+ fputs(_(" -x, --fillrows fill rows before columns\n"), out);
+
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(34));
+ printf(USAGE_MAN_TAIL("column(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ struct column_control ctl = {
+ .mode = COLUMN_MODE_FILLCOLS,
+ .greedy = 1,
+ .termwidth = (size_t) -1
+ };
+
+ int c;
+ unsigned int eval = 0; /* exit value */
+
+ static const struct option longopts[] =
+ {
+ { "columns", required_argument, NULL, 'c' }, /* deprecated */
+ { "fillrows", no_argument, NULL, 'x' },
+ { "help", no_argument, NULL, 'h' },
+ { "json", no_argument, NULL, 'J' },
+ { "output-separator", required_argument, NULL, 'o' },
+ { "output-width", required_argument, NULL, 'c' },
+ { "separator", required_argument, NULL, 's' },
+ { "table", no_argument, NULL, 't' },
+ { "table-columns", required_argument, NULL, 'N' },
+ { "table-hide", required_argument, NULL, 'H' },
+ { "table-name", required_argument, NULL, 'n' },
+ { "table-noextreme", required_argument, NULL, 'E' },
+ { "table-noheadings", no_argument, NULL, 'd' },
+ { "table-order", required_argument, NULL, 'O' },
+ { "table-right", required_argument, NULL, 'R' },
+ { "table-truncate", required_argument, NULL, 'T' },
+ { "table-wrap", required_argument, NULL, 'W' },
+ { "table-empty-lines", no_argument, NULL, 'L' },
+ { "table-header-repeat", no_argument, NULL, 'e' },
+ { "tree", required_argument, NULL, 'r' },
+ { "tree-id", required_argument, NULL, 'i' },
+ { "tree-parent", required_argument, NULL, 'p' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 },
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'J','x' },
+ { 't','x' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ ctl.output_separator = " ";
+ ctl.input_separator = mbs_to_wcs("\t ");
+
+ while ((c = getopt_long(argc, argv, "c:dE:eH:hi:JLN:n:O:o:p:R:r:s:T:tVW:x", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ case 'c':
+ ctl.termwidth = strtou32_or_err(optarg, _("invalid columns argument"));
+ break;
+ case 'd':
+ ctl.tab_noheadings = 1;
+ break;
+ case 'E':
+ ctl.tab_colnoextrem = optarg;
+ break;
+ case 'e':
+ ctl.header_repeat = 1;
+ break;
+ case 'H':
+ ctl.tab_colhide = optarg;
+ break;
+ case 'i':
+ ctl.tree_id = optarg;
+ break;
+ case 'J':
+ ctl.json = 1;
+ ctl.mode = COLUMN_MODE_TABLE;
+ break;
+ case 'L':
+ ctl.tab_empty_lines = 1;
+ break;
+ case 'N':
+ ctl.tab_colnames = split_or_error(optarg, _("failed to parse column names"));
+ break;
+ case 'n':
+ ctl.tab_name = optarg;
+ break;
+ case 'O':
+ ctl.tab_order = optarg;
+ break;
+ case 'o':
+ ctl.output_separator = optarg;
+ break;
+ case 'p':
+ ctl.tree_parent = optarg;
+ break;
+ case 'R':
+ ctl.tab_colright = optarg;
+ break;
+ case 'r':
+ ctl.tree = optarg;
+ break;
+ case 's':
+ free(ctl.input_separator);
+ ctl.input_separator = mbs_to_wcs(optarg);
+ ctl.greedy = 0;
+ break;
+ case 'T':
+ ctl.tab_coltrunc = optarg;
+ break;
+ case 't':
+ ctl.mode = COLUMN_MODE_TABLE;
+ break;
+ case 'W':
+ ctl.tab_colwrap = optarg;
+ break;
+ case 'x':
+ ctl.mode = COLUMN_MODE_FILLROWS;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (ctl.termwidth == (size_t) -1)
+ ctl.termwidth = get_terminal_width(80);
+
+ if (ctl.tree) {
+ ctl.mode = COLUMN_MODE_TABLE;
+ if (!ctl.tree_parent || !ctl.tree_id)
+ errx(EXIT_FAILURE, _("options --tree-id and --tree-parent are "
+ "required for tree formatting"));
+ }
+
+ if (ctl.mode != COLUMN_MODE_TABLE
+ && (ctl.tab_order || ctl.tab_name || ctl.tab_colwrap ||
+ ctl.tab_colhide || ctl.tab_coltrunc || ctl.tab_colnoextrem ||
+ ctl.tab_colright || ctl.tab_colnames))
+ errx(EXIT_FAILURE, _("option --table required for all --table-*"));
+
+ if (ctl.tab_colnames == NULL && ctl.json)
+ errx(EXIT_FAILURE, _("option --table-columns required for --json"));
+
+ if (!*argv)
+ eval += read_input(&ctl, stdin);
+ else
+ for (; *argv; ++argv) {
+ FILE *fp;
+
+ if ((fp = fopen(*argv, "r")) != NULL) {
+ eval += read_input(&ctl, fp);
+ fclose(fp);
+ } else {
+ warn("%s", *argv);
+ eval += EXIT_FAILURE;
+ }
+ }
+
+ if (ctl.mode != COLUMN_MODE_TABLE) {
+ if (!ctl.nents)
+ exit(eval);
+ if (ctl.maxlength >= ctl.termwidth)
+ ctl.mode = COLUMN_MODE_SIMPLE;
+ }
+
+ switch (ctl.mode) {
+ case COLUMN_MODE_TABLE:
+ if (ctl.tab && scols_table_get_nlines(ctl.tab)) {
+ modify_table(&ctl);
+ eval = scols_print_table(ctl.tab);
+ }
+ break;
+ case COLUMN_MODE_FILLCOLS:
+ columnate_fillcols(&ctl);
+ break;
+ case COLUMN_MODE_FILLROWS:
+ columnate_fillrows(&ctl);
+ break;
+ case COLUMN_MODE_SIMPLE:
+ simple_print(&ctl);
+ break;
+ }
+
+ return eval == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/text-utils/hexdump-conv.c b/text-utils/hexdump-conv.c
new file mode 100644
index 0000000..bd69709
--- /dev/null
+++ b/text-utils/hexdump-conv.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 1989 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include "hexdump.h"
+#include "xalloc.h"
+
+void
+conv_c(struct hexdump_pr *pr, u_char *p)
+{
+ char *buf = NULL;
+ char const *str;
+
+ switch(*p) {
+ case '\0':
+ str = "\\0";
+ goto strpr;
+ /* case '\a': */
+ case '\007':
+ str = "\\a";
+ goto strpr;
+ case '\b':
+ str = "\\b";
+ goto strpr;
+ case '\f':
+ str = "\\f";
+ goto strpr;
+ case '\n':
+ str = "\\n";
+ goto strpr;
+ case '\r':
+ str = "\\r";
+ goto strpr;
+ case '\t':
+ str = "\\t";
+ goto strpr;
+ case '\v':
+ str = "\\v";
+ goto strpr;
+ default:
+ break;
+ }
+ if (isprint(*p)) {
+ *pr->cchar = 'c';
+ printf(pr->fmt, *p);
+ } else {
+ xasprintf(&buf, "%03o", *p);
+ str = buf;
+strpr: *pr->cchar = 's';
+ printf(pr->fmt, str);
+ }
+ free(buf);
+}
+
+void
+conv_u(struct hexdump_pr *pr, u_char *p)
+{
+ static const char *list[] = {
+ "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
+ "bs", "ht", "lf", "vt", "ff", "cr", "so", "si",
+ "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
+ "can", "em", "sub", "esc", "fs", "gs", "rs", "us",
+ };
+
+ /* od used nl, not lf */
+ if (*p <= 0x1f) {
+ *pr->cchar = 's';
+ printf(pr->fmt, list[*p]);
+ } else if (*p == 0x7f) {
+ *pr->cchar = 's';
+ printf(pr->fmt, "del");
+ } else if (isprint(*p)) {
+ *pr->cchar = 'c';
+ printf(pr->fmt, *p);
+ } else {
+ *pr->cchar = 'x';
+ printf(pr->fmt, *p);
+ }
+}
diff --git a/text-utils/hexdump-display.c b/text-utils/hexdump-display.c
new file mode 100644
index 0000000..695b472
--- /dev/null
+++ b/text-utils/hexdump-display.c
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 1989 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "hexdump.h"
+#include "xalloc.h"
+#include "c.h"
+#include "nls.h"
+#include "colors.h"
+
+static void doskip(const char *, int, struct hexdump *);
+static u_char *get(struct hexdump *);
+
+enum _vflag vflag = FIRST;
+
+static off_t address; /* address/offset in stream */
+static off_t eaddress; /* end address */
+
+static const char *color_cond(struct hexdump_pr *pr, unsigned char *bp, int bcnt)
+{
+ register struct list_head *p;
+ register struct hexdump_clr *clr;
+ off_t offt;
+ int match;
+
+ list_for_each(p, pr->colorlist) {
+ clr = list_entry(p, struct hexdump_clr, colorlist);
+ offt = clr->offt;
+ match = 0;
+
+ /* no offset or offset outside this print unit */
+ if (offt < 0)
+ offt = address;
+ if (offt < address || offt + clr->range > address + bcnt)
+ continue;
+
+ /* match a string */
+ if (clr->str) {
+ if (pr->flags == F_ADDRESS) {
+ /* TODO */
+ }
+ else if (!strncmp(clr->str, (char *)bp + offt
+ - address, clr->range))
+ match = 1;
+ /* match a value */
+ } else if (clr->val != -1) {
+ int val = 0;
+ /* addresses are not part of the input, so we can't
+ * compare with the contents of bp */
+ if (pr->flags == F_ADDRESS) {
+ if (clr->val == address)
+ match = 1;
+ } else {
+ memcpy(&val, bp + offt - address, clr->range);
+ if (val == clr->val)
+ match = 1;
+ }
+ /* no conditions, only a color was specified */
+ } else
+ return clr->fmt;
+
+ /* return the format string or check for another */
+ if (match ^ clr->invert)
+ return clr->fmt;
+ }
+
+ /* no match */
+ return NULL;
+}
+
+static inline void
+print(struct hexdump_pr *pr, unsigned char *bp) {
+
+ const char *color = NULL;
+
+ if (pr->colorlist && (color = color_cond(pr, bp, pr->bcnt)))
+ color_enable(color);
+
+ switch(pr->flags) {
+ case F_ADDRESS:
+ printf(pr->fmt, address);
+ break;
+ case F_BPAD:
+ printf(pr->fmt, "");
+ break;
+ case F_C:
+ conv_c(pr, bp);
+ break;
+ case F_CHAR:
+ printf(pr->fmt, *bp);
+ break;
+ case F_DBL:
+ {
+ double dval;
+ float fval;
+ switch(pr->bcnt) {
+ case 4:
+ memmove(&fval, bp, sizeof(fval));
+ printf(pr->fmt, fval);
+ break;
+ case 8:
+ memmove(&dval, bp, sizeof(dval));
+ printf(pr->fmt, dval);
+ break;
+ }
+ break;
+ }
+ case F_INT:
+ {
+ short sval; /* int16_t */
+ int ival; /* int32_t */
+ long long Lval; /* int64_t, int64_t */
+
+ switch(pr->bcnt) {
+ case 1:
+ printf(pr->fmt, (unsigned long long) *bp);
+ break;
+ case 2:
+ memmove(&sval, bp, sizeof(sval));
+ printf(pr->fmt, (unsigned long long) sval);
+ break;
+ case 4:
+ memmove(&ival, bp, sizeof(ival));
+ printf(pr->fmt, (unsigned long long) ival);
+ break;
+ case 8:
+ memmove(&Lval, bp, sizeof(Lval));
+ printf(pr->fmt, Lval);
+ break;
+ }
+ break;
+ }
+ case F_P:
+ printf(pr->fmt, isprint(*bp) ? *bp : '.');
+ break;
+ case F_STR:
+ printf(pr->fmt, (char *)bp);
+ break;
+ case F_TEXT:
+ printf("%s", pr->fmt);
+ break;
+ case F_U:
+ conv_u(pr, bp);
+ break;
+ case F_UINT:
+ {
+ unsigned short sval; /* u_int16_t */
+ unsigned int ival; /* u_int32_t */
+ unsigned long long Lval;/* u_int64_t, u_int64_t */
+
+ switch(pr->bcnt) {
+ case 1:
+ printf(pr->fmt, (unsigned long long) *bp);
+ break;
+ case 2:
+ memmove(&sval, bp, sizeof(sval));
+ printf(pr->fmt, (unsigned long long) sval);
+ break;
+ case 4:
+ memmove(&ival, bp, sizeof(ival));
+ printf(pr->fmt, (unsigned long long) ival);
+ break;
+ case 8:
+ memmove(&Lval, bp, sizeof(Lval));
+ printf(pr->fmt, Lval);
+ break;
+ }
+ break;
+ }
+ }
+ if (color) /* did we colorize something? */
+ color_disable();
+}
+
+static void bpad(struct hexdump_pr *pr)
+{
+ static const char *spec = " -0+#";
+ char *p1, *p2;
+
+ /*
+ * remove all conversion flags; '-' is the only one valid
+ * with %s, and it's not useful here.
+ */
+ pr->flags = F_BPAD;
+ pr->cchar[0] = 's';
+ pr->cchar[1] = 0;
+
+ p1 = pr->fmt;
+ while (*p1 != '%')
+ ++p1;
+
+ p2 = ++p1;
+ while (*p1 && strchr(spec, *p1))
+ ++p1;
+
+ while ((*p2++ = *p1++))
+ ;
+}
+
+void display(struct hexdump *hex)
+{
+ register struct list_head *fs;
+ register struct hexdump_fs *fss;
+ register struct hexdump_fu *fu;
+ register struct hexdump_pr *pr;
+ register int cnt;
+ register unsigned char *bp;
+ off_t saveaddress;
+ unsigned char savech = 0, *savebp;
+ struct list_head *p, *q, *r;
+
+ while ((bp = get(hex)) != NULL) {
+ fs = &hex->fshead; savebp = bp; saveaddress = address;
+
+ list_for_each(p, fs) {
+ fss = list_entry(p, struct hexdump_fs, fslist);
+
+ list_for_each(q, &fss->fulist) {
+ fu = list_entry(q, struct hexdump_fu, fulist);
+
+ if (fu->flags&F_IGNORE)
+ break;
+
+ cnt = fu->reps;
+
+ while (cnt) {
+ list_for_each(r, &fu->prlist) {
+ pr = list_entry(r, struct hexdump_pr, prlist);
+
+ if (eaddress && address >= eaddress
+ && !(pr->flags&(F_TEXT|F_BPAD)))
+ bpad(pr);
+
+ if (cnt == 1 && pr->nospace) {
+ savech = *pr->nospace;
+ *pr->nospace = '\0';
+ print(pr, bp);
+ *pr->nospace = savech;
+ } else
+ print(pr, bp);
+
+ address += pr->bcnt;
+ bp += pr->bcnt;
+ }
+ --cnt;
+ }
+ }
+ bp = savebp;
+ address = saveaddress;
+ }
+ }
+ if (endfu) {
+ /*
+ * if eaddress not set, error or file size was multiple of
+ * blocksize, and no partial block ever found.
+ */
+ if (!eaddress) {
+ if (!address)
+ return;
+ eaddress = address;
+ }
+ list_for_each (p, &endfu->prlist) {
+ const char *color = NULL;
+
+ pr = list_entry(p, struct hexdump_pr, prlist);
+ if (colors_wanted() && pr->colorlist
+ && (color = color_cond(pr, bp, pr->bcnt))) {
+ color_enable(color);
+ }
+
+ switch(pr->flags) {
+ case F_ADDRESS:
+ printf(pr->fmt, eaddress);
+ break;
+ case F_TEXT:
+ printf("%s", pr->fmt);
+ break;
+ }
+ if (color) /* did we highlight something? */
+ color_disable();
+ }
+ }
+}
+
+static char **_argv;
+
+static u_char *
+get(struct hexdump *hex)
+{
+ static int ateof = 1;
+ static u_char *curp, *savp;
+ ssize_t n, need, nread;
+ u_char *tmpp;
+
+ if (!curp) {
+ curp = xcalloc(1, hex->blocksize);
+ savp = xcalloc(1, hex->blocksize);
+ } else {
+ tmpp = curp;
+ curp = savp;
+ savp = tmpp;
+ address += hex->blocksize;
+ }
+ need = hex->blocksize, nread = 0;
+ while (TRUE) {
+ /*
+ * if read the right number of bytes, or at EOF for one file,
+ * and no other files are available, zero-pad the rest of the
+ * block and set the end flag.
+ */
+ if (!hex->length || (ateof && !next(NULL, hex))) {
+ if (need == hex->blocksize)
+ goto retnul;
+ if (!need && vflag != ALL &&
+ !memcmp(curp, savp, nread)) {
+ if (vflag != DUP)
+ printf("*\n");
+ goto retnul;
+ }
+ if (need > 0)
+ memset((char *)curp + nread, 0, need);
+ eaddress = address + nread;
+ return(curp);
+ }
+ if (fileno(stdin) == -1) {
+ warnx(_("all input file arguments failed"));
+ goto retnul;
+ }
+ n = fread((char *)curp + nread, sizeof(unsigned char),
+ hex->length == -1 ? need : min(hex->length, need), stdin);
+ if (!n) {
+ if (ferror(stdin))
+ warn("%s", _argv[-1]);
+ ateof = 1;
+ continue;
+ }
+ ateof = 0;
+ if (hex->length != -1)
+ hex->length -= n;
+ if (!(need -= n)) {
+ if (vflag == ALL || vflag == FIRST ||
+ memcmp(curp, savp, hex->blocksize) != 0) {
+ if (vflag == DUP || vflag == FIRST)
+ vflag = WAIT;
+ return(curp);
+ }
+ if (vflag == WAIT)
+ printf("*\n");
+ vflag = DUP;
+ address += hex->blocksize;
+ need = hex->blocksize;
+ nread = 0;
+ }
+ else
+ nread += n;
+ }
+retnul:
+ free (curp);
+ free (savp);
+ return NULL;
+}
+
+int next(char **argv, struct hexdump *hex)
+{
+ static int done;
+ int statok;
+
+ if (argv) {
+ _argv = argv;
+ return(1);
+ }
+ while (TRUE) {
+ if (*_argv) {
+ if (!(freopen(*_argv, "r", stdin))) {
+ warn("%s", *_argv);
+ hex->exitval = EXIT_FAILURE;
+ ++_argv;
+ continue;
+ }
+ statok = done = 1;
+ } else {
+ if (done++)
+ return(0);
+ statok = 0;
+ }
+ if (hex->skip)
+ doskip(statok ? *_argv : "stdin", statok, hex);
+ if (*_argv)
+ ++_argv;
+ if (!hex->skip)
+ return(1);
+ }
+ /* NOTREACHED */
+}
+
+static void
+doskip(const char *fname, int statok, struct hexdump *hex)
+{
+ struct stat sbuf;
+
+ if (statok) {
+ if (fstat(fileno(stdin), &sbuf))
+ err(EXIT_FAILURE, "%s", fname);
+ if (S_ISREG(sbuf.st_mode) && hex->skip > sbuf.st_size) {
+ /* If size valid and skip >= size */
+ hex->skip -= sbuf.st_size;
+ address += sbuf.st_size;
+ return;
+ }
+ }
+ /* sbuf may be undefined here - do not test it */
+ if (fseek(stdin, hex->skip, SEEK_SET))
+ err(EXIT_FAILURE, "%s", fname);
+ address += hex->skip;
+ hex->skip = 0;
+}
diff --git a/text-utils/hexdump-parse.c b/text-utils/hexdump-parse.c
new file mode 100644
index 0000000..0b460a7
--- /dev/null
+++ b/text-utils/hexdump-parse.c
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 1989 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+ /* 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include "hexdump.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "colors.h"
+
+static void escape(char *p1);
+static struct list_head *color_fmt(char *cfmt, int bcnt);
+
+static void __attribute__ ((__noreturn__)) badcnt(const char *s)
+{
+ errx(EXIT_FAILURE, _("bad byte count for conversion character %s"), s);
+}
+
+static void __attribute__ ((__noreturn__)) badsfmt(void)
+{
+ errx(EXIT_FAILURE, _("%%s requires a precision or a byte count"));
+}
+
+static void __attribute__ ((__noreturn__)) badfmt(const char *fmt)
+{
+ errx(EXIT_FAILURE, _("bad format {%s}"), fmt);
+}
+
+static void __attribute__ ((__noreturn__)) badconv(const char *ch)
+{
+ errx(EXIT_FAILURE, _("bad conversion character %%%s"), ch);
+}
+
+#define first_letter(s,f) strchr(f, *(s))
+
+struct hexdump_fu *endfu; /* format at end-of-data */
+
+void addfile(char *name, struct hexdump *hex)
+{
+ char *fmt, *buf = NULL;
+ FILE *fp;
+ size_t n;
+
+ if ((fp = fopen(name, "r")) == NULL)
+ err(EXIT_FAILURE, _("can't read %s"), name);
+
+ while (getline(&buf, &n, fp) != -1) {
+ fmt = buf;
+
+ while (*fmt && isspace(*fmt))
+ ++fmt;
+ if (!*fmt || *fmt == '#')
+ continue;
+
+ add_fmt(fmt, hex);
+ }
+
+ free(buf);
+ fclose(fp);
+}
+
+void add_fmt(const char *fmt, struct hexdump *hex)
+{
+ const char *p, *savep;
+ struct hexdump_fs *tfs;
+ struct hexdump_fu *tfu;
+
+ /* Start new linked list of format units. */
+ tfs = xcalloc(1, sizeof(struct hexdump_fs));
+ INIT_LIST_HEAD(&tfs->fslist);
+ INIT_LIST_HEAD(&tfs->fulist);
+ list_add_tail(&tfs->fslist, &hex->fshead);
+
+ /* Take the format string and break it up into format units. */
+ p = fmt;
+ while (TRUE) {
+ /* Skip leading white space. */
+ if (!*(p = skip_space(p)))
+ break;
+
+ /* Allocate a new format unit and link it in. */
+ tfu = xcalloc(1, sizeof(struct hexdump_fu));
+ tfu->reps = 1;
+
+ INIT_LIST_HEAD(&tfu->fulist);
+ INIT_LIST_HEAD(&tfu->prlist);
+ list_add_tail(&tfu->fulist, &tfs->fulist);
+
+ /* If leading digit, repetition count. */
+ if (isdigit(*p)) {
+ savep = p;
+ while (isdigit(*p))
+ p++;
+ if (!isspace(*p) && *p != '/')
+ badfmt(fmt);
+ /* may overwrite either white space or slash */
+ tfu->reps = atoi(savep);
+ tfu->flags = F_SETREP;
+ /* skip trailing white space */
+ p = skip_space(++p);
+ }
+
+ /* Skip slash and trailing white space. */
+ if (*p == '/')
+ p = skip_space(++p);
+
+ /* byte count */
+ if (isdigit(*p)) {
+ savep = p;
+ while (isdigit(*p))
+ p++;
+ if (!isspace(*p))
+ badfmt(fmt);
+ tfu->bcnt = atoi(savep);
+ /* skip trailing white space */
+ p = skip_space(++p);
+ }
+
+ /* format */
+ if (*p != '"')
+ badfmt(fmt);
+ savep = ++p;
+ while (*p != '"') {
+ if (!*p++)
+ badfmt(fmt);
+ }
+ tfu->fmt = xmalloc(p - savep + 1);
+ xstrncpy(tfu->fmt, savep, p - savep + 1);
+ escape(tfu->fmt);
+ ++p;
+ }
+}
+
+static const char *spec = ".#-+ 0123456789";
+
+int block_size(struct hexdump_fs *fs)
+{
+ struct hexdump_fu *fu;
+ int bcnt, prec, cursize = 0;
+ char *fmt;
+ struct list_head *p;
+
+ /* figure out the data block size needed for each format unit */
+ list_for_each (p, &fs->fulist) {
+ fu = list_entry(p, struct hexdump_fu, fulist);
+ if (fu->bcnt) {
+ cursize += fu->bcnt * fu->reps;
+ continue;
+ }
+ bcnt = prec = 0;
+ fmt = fu->fmt;
+ while (*fmt) {
+ if (*fmt != '%') {
+ ++fmt;
+ continue;
+ }
+ /*
+ * skip any special chars -- save precision in
+ * case it's a %s format.
+ */
+ while (strchr(spec + 1, *++fmt))
+ ;
+ if (*fmt == '.' && isdigit(*++fmt)) {
+ prec = atoi(fmt);
+ while (isdigit(*++fmt))
+ ;
+ }
+ if (first_letter(fmt, "diouxX"))
+ bcnt += 4;
+ else if (first_letter(fmt, "efgEG"))
+ bcnt += 8;
+ else if (*fmt == 's')
+ bcnt += prec;
+ else if (*fmt == 'c' || (*fmt == '_' && first_letter(++fmt, "cpu")))
+ ++bcnt;
+ ++fmt;
+ }
+ cursize += bcnt * fu->reps;
+ }
+ return(cursize);
+}
+
+void rewrite_rules(struct hexdump_fs *fs, struct hexdump *hex)
+{
+ enum { NOTOKAY, USEBCNT, USEPREC } sokay;
+ struct hexdump_pr *pr;
+ struct hexdump_fu *fu;
+ struct list_head *p, *q;
+ char *p1, *p2, *fmtp;
+ char savech, cs[4];
+ int nconv, prec = 0;
+
+ list_for_each (p, &fs->fulist) {
+ fu = list_entry(p, struct hexdump_fu, fulist);
+ /*
+ * Break each format unit into print units; each
+ * conversion character gets its own.
+ */
+ nconv = 0;
+ fmtp = fu->fmt;
+ while (*fmtp) {
+ pr = xcalloc(1, sizeof(struct hexdump_pr));
+ INIT_LIST_HEAD(&pr->prlist);
+ list_add_tail(&pr->prlist, &fu->prlist);
+
+ /* Skip preceding text and up to the next % sign. */
+ p1 = fmtp;
+ while (*p1 && *p1 != '%')
+ ++p1;
+
+ /* Only text in the string. */
+ if (!*p1) {
+ pr->fmt = xstrdup(fmtp);
+ pr->flags = F_TEXT;
+ break;
+ }
+
+ /*
+ * Get precision for %s -- if have a byte count, don't
+ * need it.
+ */
+ if (fu->bcnt) {
+ sokay = USEBCNT;
+ /* skip to conversion character */
+ for (p1++; strchr(spec, *p1); p1++)
+ ;
+ } else {
+ /* skip any special chars, field width */
+ while (strchr(spec + 1, *++p1))
+ ;
+ if (*p1 == '.' && isdigit(*++p1)) {
+ sokay = USEPREC;
+ prec = atoi(p1);
+ while (isdigit(*++p1))
+ ;
+ } else
+ sokay = NOTOKAY;
+ }
+
+ p2 = p1 + 1; /* Set end pointer. */
+ cs[0] = *p1; /* Set conversion string. */
+ cs[1] = 0;
+
+ /*
+ * Figure out the byte count for each conversion;
+ * rewrite the format as necessary, set up blank-
+ * padding for end of data.
+ */
+ if (*cs == 'c') {
+ pr->flags = F_CHAR;
+ switch(fu->bcnt) {
+ case 0:
+ case 1:
+ pr->bcnt = 1;
+ break;
+ default:
+ p1[1] = '\0';
+ badcnt(p1);
+ }
+ } else if (first_letter(cs, "di")) {
+ pr->flags = F_INT;
+ goto isint;
+ } else if (first_letter(cs, "ouxX")) {
+ pr->flags = F_UINT;
+isint: cs[3] = '\0';
+ cs[2] = cs[0];
+ cs[1] = 'l';
+ cs[0] = 'l';
+ switch(fu->bcnt) {
+ case 0:
+ pr->bcnt = 4;
+ break;
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ pr->bcnt = fu->bcnt;
+ break;
+ default:
+ p1[1] = '\0';
+ badcnt(p1);
+ }
+ } else if (first_letter(cs, "efgEG")) {
+ pr->flags = F_DBL;
+ switch(fu->bcnt) {
+ case 0:
+ pr->bcnt = 8;
+ break;
+ case 4:
+ case 8:
+ pr->bcnt = fu->bcnt;
+ break;
+ default:
+ p1[1] = '\0';
+ badcnt(p1);
+ }
+ } else if(*cs == 's') {
+ pr->flags = F_STR;
+ switch(sokay) {
+ case NOTOKAY:
+ badsfmt();
+ case USEBCNT:
+ pr->bcnt = fu->bcnt;
+ break;
+ case USEPREC:
+ pr->bcnt = prec;
+ break;
+ }
+ } else if (*cs == '_') {
+ ++p2;
+ switch(p1[1]) {
+ case 'A':
+ endfu = fu;
+ fu->flags |= F_IGNORE;
+ /* fallthrough */
+ case 'a':
+ pr->flags = F_ADDRESS;
+ ++p2;
+ if (first_letter(p1 + 2, "dox")) {
+ cs[0] = 'l';
+ cs[1] = 'l';
+ cs[2] = p1[2];
+ cs[3] = '\0';
+ } else {
+ p1[3] = '\0';
+ badconv(p1);
+ }
+ break;
+ case 'c':
+ pr->flags = F_C;
+ /* cs[0] = 'c'; set in conv_c */
+ goto isint2;
+ case 'p':
+ pr->flags = F_P;
+ cs[0] = 'c';
+ goto isint2;
+ case 'u':
+ pr->flags = F_U;
+ /* cs[0] = 'c'; set in conv_u */
+ isint2: switch(fu->bcnt) {
+ case 0:
+ case 1:
+ pr->bcnt = 1;
+ break;
+ default:
+ p1[2] = '\0';
+ badcnt(p1);
+ }
+ break;
+ default:
+ p1[2] = '\0';
+ badconv(p1);
+ }
+ } else {
+ p1[1] = '\0';
+ badconv(p1);
+ }
+
+ /* Color unit(s) specified */
+ if (*p2 == '_' && p2[1] == 'L') {
+ if (colors_wanted()) {
+ char *a;
+
+ /* "cut out" the color_unit(s) */
+ a = strchr(p2, '[');
+ p2 = strrchr(p2, ']');
+ if (a++ && p2)
+ pr->colorlist = color_fmt(xstrndup(a, p2++ - a), pr->bcnt);
+ else
+ badconv(p2);
+ }
+ /* we don't want colors, quietly skip over them */
+ else {
+ p2 = strrchr(p2, ']');
+ /* be a bit louder if we don't know how to skip over them */
+ if (!p2)
+ badconv("_L");
+ ++p2;
+ }
+ }
+ /*
+ * Copy to hexdump_pr format string, set conversion character
+ * pointer, update original.
+ */
+ savech = *p2;
+ p1[0] = '\0';
+ pr->fmt = xmalloc(strlen(fmtp) + strlen(cs) + 1);
+ strcpy(pr->fmt, fmtp);
+ strcat(pr->fmt, cs);
+ *p2 = savech;
+ pr->cchar = pr->fmt + (p1 - fmtp);
+ fmtp = p2;
+
+ /* Only one conversion character if byte count */
+ if (!(pr->flags&F_ADDRESS) && fu->bcnt && nconv++)
+ errx(EXIT_FAILURE,
+ _("byte count with multiple conversion characters"));
+ }
+ /*
+ * If format unit byte count not specified, figure it out
+ * so can adjust rep count later.
+ */
+ if (!fu->bcnt)
+ list_for_each(q, &fu->prlist)
+ fu->bcnt
+ += (list_entry(q, struct hexdump_pr, prlist))->bcnt;
+ }
+ /*
+ * If the format string interprets any data at all, and it's
+ * not the same as the blocksize, and its last format unit
+ * interprets any data at all, and has no iteration count,
+ * repeat it as necessary.
+ *
+ * If rep count is greater than 1, no trailing whitespace
+ * gets output from the last iteration of the format unit.
+ */
+ list_for_each (p, &fs->fulist) {
+ fu = list_entry(p, struct hexdump_fu, fulist);
+
+ if (list_entry_is_last(&fu->fulist, &fs->fulist) &&
+ fs->bcnt < hex->blocksize &&
+ !(fu->flags&F_SETREP) && fu->bcnt)
+ fu->reps += (hex->blocksize - fs->bcnt) / fu->bcnt;
+ if (fu->reps > 1 && !list_empty(&fu->prlist)) {
+ pr = list_last_entry(&fu->prlist, struct hexdump_pr, prlist);
+ if (!pr)
+ continue;
+ for (p1 = pr->fmt, p2 = NULL; *p1; ++p1)
+ p2 = isspace(*p1) ? p1 : NULL;
+ if (p2)
+ pr->nospace = p2;
+ }
+ }
+}
+
+/* [!]color[:string|:hex_number|:oct_number][@offt|@offt_start-offt_end],... */
+static struct list_head *color_fmt(char *cfmt, int bcnt)
+{
+ struct hexdump_clr *hc, *hcnext;
+ struct list_head *ret_head;
+ char *clr, *fmt;
+
+ ret_head = xmalloc(sizeof(struct list_head));
+ hcnext = hc = xcalloc(1, sizeof(struct hexdump_clr));
+
+ INIT_LIST_HEAD(&hc->colorlist);
+ INIT_LIST_HEAD(ret_head);
+ list_add_tail(&hc->colorlist, ret_head);
+
+ fmt = cfmt;
+ while (cfmt && *cfmt) {
+ char *end;
+ /* invert this condition */
+ if (*cfmt == '!') {
+ hcnext->invert = 1;
+ ++cfmt;
+ }
+
+ clr = xstrndup(cfmt, strcspn(cfmt, ":@,"));
+ cfmt += strlen(clr);
+ hcnext->fmt = color_sequence_from_colorname(clr);
+ free(clr);
+
+ if (!hcnext->fmt)
+ return NULL;
+
+ /* only colorize this specific value */
+ if (*cfmt == ':') {
+ ++cfmt;
+ /* a hex or oct value */
+ if (*cfmt == '0') {
+ /* hex */
+ errno = 0;
+ end = NULL;
+ if (cfmt[1] == 'x' || cfmt[1] == 'X')
+ hcnext->val = strtoul(cfmt + 2, &end, 16);
+ else
+ hcnext->val = strtoul(cfmt, &end, 8);
+ if (errno || end == cfmt)
+ badfmt(fmt);
+ cfmt = end;
+ /* a string */
+ } else {
+ off_t fmt_end;
+ char endchar;
+ char *endstr;
+
+ hcnext->val = -1;
+ /* temporarily null-delimit the format, so we can reverse-search
+ * for the start of an offset specifier */
+ fmt_end = strcspn(cfmt, ",");
+ endchar = cfmt[fmt_end];
+ cfmt[fmt_end] = '\0';
+ endstr = strrchr(cfmt, '@');
+
+ if (endstr) {
+ if (endstr[1] != '\0')
+ --endstr;
+ hcnext->str = xstrndup(cfmt, endstr - cfmt + 1);
+ } else
+ hcnext->str = xstrndup(cfmt, fmt_end);
+
+ /* restore the character */
+ cfmt[fmt_end] = endchar;
+ cfmt += strlen(hcnext->str);
+ }
+
+ /* no specific value */
+ } else
+ hcnext->val = -1;
+
+ /* only colorize at this offset */
+ hcnext->range = bcnt;
+ if (cfmt && *cfmt == '@') {
+ errno = 0;
+ hcnext->offt = strtoul(++cfmt, &cfmt, 10);
+ if (errno)
+ badfmt(fmt);
+
+ /* offset range */
+ if (*cfmt == '-') {
+ ++cfmt;
+ errno = 0;
+
+ hcnext->range =
+ strtoul(cfmt, &cfmt, 10) - hcnext->offt + 1;
+ if (errno)
+ badfmt(fmt);
+ /* offset range must be between 0 and format byte count */
+ if (hcnext->range < 0)
+ badcnt("_L");
+ /* the offset extends over several print units, clone
+ * the condition, link it in and adjust the address/offset */
+ while (hcnext->range > bcnt) {
+ hc = xcalloc(1, sizeof(struct hexdump_clr));
+ memcpy(hc, hcnext, sizeof(struct hexdump_clr));
+
+ hc->range = bcnt;
+
+ INIT_LIST_HEAD(&hc->colorlist);
+ list_add_tail(&hc->colorlist, ret_head);
+
+ hcnext->offt += bcnt;
+ hcnext->range -= bcnt;
+ }
+ }
+ /* no specific offset */
+ } else
+ hcnext->offt = (off_t)-1;
+
+ /* check if the string we're looking for is the same length as the range */
+ if (hcnext->str && (int)strlen(hcnext->str) != hcnext->range)
+ badcnt("_L");
+
+ /* link in another condition */
+ if (cfmt && *cfmt == ',') {
+ ++cfmt;
+
+ hcnext = xcalloc(1, sizeof(struct hexdump_clr));
+ INIT_LIST_HEAD(&hcnext->colorlist);
+ list_add_tail(&hcnext->colorlist, ret_head);
+ }
+ }
+ return ret_head;
+}
+
+static void escape(char *p1)
+{
+ char *p2;
+
+ /* alphabetic escape sequences have to be done in place */
+ p2 = p1;
+ while (TRUE) {
+ if (!*p1) {
+ *p2 = *p1;
+ break;
+ }
+ if (*p1 == '\\')
+ switch(*++p1) {
+ case 'a':
+ /* *p2 = '\a'; */
+ *p2 = '\007';
+ break;
+ case 'b':
+ *p2 = '\b';
+ break;
+ case 'f':
+ *p2 = '\f';
+ break;
+ case 'n':
+ *p2 = '\n';
+ break;
+ case 'r':
+ *p2 = '\r';
+ break;
+ case 't':
+ *p2 = '\t';
+ break;
+ case 'v':
+ *p2 = '\v';
+ break;
+ default:
+ *p2 = *p1;
+ break;
+ }
+ ++p1; ++p2;
+ }
+}
diff --git a/text-utils/hexdump.1 b/text-utils/hexdump.1
new file mode 100644
index 0000000..99b9d06
--- /dev/null
+++ b/text-utils/hexdump.1
@@ -0,0 +1,382 @@
+.\" Copyright (c) 1989, 1990, 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)hexdump.1 8.2 (Berkeley) 4/18/94
+.\"
+.TH HEXDUMP "1" "April 2013" "util-linux" "User Commands"
+.SH NAME
+hexdump \- display file contents in hexadecimal, decimal, octal, or ascii
+.SH SYNOPSIS
+.B hexdump
+.RI [options] " file" ...
+.SH DESCRIPTION
+The
+.B hexdump
+utility is a filter which displays the specified files, or
+standard input if no files are specified, in a user-specified
+format.
+.SH OPTIONS
+Below, the \fIlength\fR and \fIoffset\fR arguments may be followed by the multiplicative
+suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB
+(the "iB" is optional, e.g., "K" has the same meaning as "KiB"), or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB.
+.TP
+\fB\-b\fR, \fB\-\-one\-byte\-octal\fR
+\fIOne-byte octal display\fR. Display the input offset in hexadecimal,
+followed by sixteen space-separated, three-column, zero-filled bytes of input
+data, in octal, per line.
+.TP
+\fB\-c\fR, \fB\-\-one\-byte\-char\fR
+\fIOne-byte character display\fR. Display the input offset in hexadecimal,
+followed by sixteen space-separated, three-column, space-filled characters of
+input data per line.
+.TP
+\fB\-C\fR, \fB\-\-canonical\fR
+\fICanonical hex+ASCII display\fR. Display the input offset in hexadecimal,
+followed by sixteen space-separated, two-column, hexadecimal bytes, followed
+by the same sixteen bytes in
+.B %_p
+format enclosed in
+.RB ' | '
+characters.
+Invoking the program as
+.B hd
+implies this option.
+.TP
+\fB\-d\fR, \fB\-\-two\-bytes\-decimal\fR
+\fITwo-byte decimal display\fR. Display the input offset in hexadecimal,
+followed by eight space-separated, five-column, zero-filled, two-byte units
+of input data, in unsigned decimal, per line.
+.TP
+\fB\-e\fR, \fB\-\-format\fR \fIformat_string\fR
+Specify a format string to be used for displaying data.
+.TP
+\fB\-f\fR, \fB\-\-format\-file\fR \fIfile\fR
+Specify a file that contains one or more newline-separated format strings.
+Empty lines and lines whose first non-blank character is a hash mark (\&#)
+are ignored.
+.TP
+\fB\-L\fR, \fB\-\-color\fR[=\fIwhen\fR]
+Accept color units for the output. The optional argument \fIwhen\fP
+can be \fBauto\fR, \fBnever\fR or \fBalways\fR. If the \fIwhen\fR argument is omitted,
+it defaults to \fBauto\fR. The colors can be disabled; for the current built-in default
+see the \fB\-\-help\fR output. See also the \fBColors\fR subsection and
+the \fBCOLORS\fR section below.
+.TP
+\fB\-n\fR, \fB\-\-length\fR \fIlength\fR
+Interpret only
+.I length
+bytes of input.
+.TP
+\fB\-o\fR, \fB\-\-two\-bytes\-octal\fR
+\fITwo-byte octal display\fR. Display the input offset in hexadecimal,
+followed by eight space-separated, six-column, zero-filled, two-byte
+quantities of input data, in octal, per line.
+.TP
+\fB\-s\fR, \fB\-\-skip\fR \fIoffset\fR
+Skip
+.I offset
+bytes from the beginning of the input.
+.TP
+\fB\-v\fR, \fB\-\-no\-squeezing\fR
+The
+.B \-v
+option causes
+.B hexdump
+to display all input data. Without the
+.B \-v
+option, any number of groups of output lines which would be identical to the
+immediately preceding group of output lines (except for the input offsets),
+are replaced with a line comprised of a single asterisk.
+.TP
+\fB\-x\fR, \fB\-\-two\-bytes\-hex\fR
+\fITwo-byte hexadecimal display\fR. Display the input offset in hexadecimal,
+followed by eight space-separated, four-column, zero-filled, two-byte
+quantities of input data, in hexadecimal, per line.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.PP
+For each input file,
+.B hexdump
+sequentially copies the input to standard output, transforming the data
+according to the format strings specified by the
+.B \-e
+and
+.B \-f
+options, in the order that they were specified.
+.SH FORMATS
+A format string contains any number of format units, separated by whitespace.
+A format unit contains up to three items: an iteration count, a byte count,
+and a format.
+.PP
+The iteration count is an optional positive integer, which defaults to one.
+Each format is applied iteration count times.
+.PP
+The byte count is an optional positive integer. If specified it defines the
+number of bytes to be interpreted by each iteration of the format.
+.PP
+If an iteration count and/or a byte count is specified, a single slash must
+be placed after the iteration count and/or before the byte count to
+disambiguate them. Any whitespace before or after the slash is ignored.
+.PP
+The format is required and must be surrounded by double quote (" ") marks.
+It is interpreted as a fprintf-style format string (see
+.BR fprintf (3),
+with the following exceptions:
+.TP
+1.
+An asterisk (*) may not be used as a field width or precision.
+.TP
+2.
+A byte count or field precision
+.I is
+required for each
+.B s
+conversion character (unlike the
+.BR fprintf (3)
+default which prints the entire string if the precision is unspecified).
+.TP
+3.
+The conversion characters
+.BR h , \ l , \ n , \ p ,
+.RB and \ q
+are not supported.
+.TP
+4.
+The single character escape sequences described in the C standard are
+supported:
+.PP
+.RS 13
+.PD 0
+.TP 21
+NULL
+\e0
+.TP
+<alert character>
+\ea
+.TP
+<backspace>
+\eb
+.TP
+<form-feed>
+\ef
+.TP
+<newline>
+\en
+.TP
+<carriage return>
+\er
+.TP
+<tab>
+\et
+.TP
+<vertical tab>
+\ev
+.PD
+.RE
+.SS Conversion strings
+The
+.B hexdump
+utility also supports the following additional conversion strings.
+.TP
+.B \&_a[dox]
+Display the input offset, cumulative across input files, of the next byte to
+be displayed. The appended characters
+.BR d ,
+.BR o ,
+and
+.B x
+specify the display base as decimal, octal or hexadecimal respectively.
+.TP
+.B \&_A[dox]
+Identical to the
+.B \&_a
+conversion string except that it is only performed once, when all of the
+input data has been processed.
+.TP
+.B \&_c
+Output characters in the default character set. Non-printing characters are
+displayed in three-character, zero-padded octal, except for those
+representable by standard escape notation (see above), which are displayed as
+two-character strings.
+.TP
+.B \&_p
+Output characters in the default character set. Non-printing characters are
+displayed as a single
+.RB ' \&. '.
+.TP
+.B \&_u
+Output US ASCII characters, with the exception that control characters are
+displayed using the following, lower-case, names. Characters greater than
+0xff, hexadecimal, are displayed as hexadecimal strings.
+.RS 10
+.TS
+tab(|);
+l l l l l l.
+000 nul|001 soh|002 stx|003 etx|004 eot|005 enq
+006 ack|007 bel|008 bs|009 ht|00A lf|00B vt
+00C ff|00D cr|00E so|00F si|010 dle|011 dc1
+012 dc2|013 dc3|014 dc4|015 nak|016 syn|017 etb
+018 can|019 em|01A sub|01B esc|01C fs|01D gs
+01E rs|01F us|0FF del
+.TE
+.RE
+.SS Colors
+When put at the end of a format specifier, hexdump highlights the
+respective string with the color specified. Conditions, if present, are
+evaluated prior to highlighting.
+.PP
+.B \&_L[color_unit_1,\:color_unit_2,\:...,\:color_unit_n]
+.PP
+The full syntax of a color unit is as follows:
+.PP
+.B [!]COLOR\:[:VALUE]\:[@OFFSET_START[-END]]
+.TP
+.B !
+Negate the condition. Please note that it only makes sense to negate a
+unit if both a value/\:string and an offset are specified. In that case
+the respective output string will be highlighted if and only if the
+value/\:string does not match the one at the offset.
+.TP
+.B COLOR
+One of the 8 basic shell colors.
+.TP
+.B VALUE
+A value to be matched specified in hexadecimal, or octal base, or as a
+string. Please note that the usual C escape sequences are not
+interpreted by hexdump inside the color_units.
+.TP
+.B OFFSET
+An offset or an offset range at which to check for a match. Please note
+that lone OFFSET_START uses the same value as END offset.
+.SS Counters
+The default and supported byte counts for the conversion characters
+are as follows:
+.TP
+.BR \&%_c , \ \&%_p , \ \&%_u , \ \&%c
+One byte counts only.
+.TP
+.BR \&%d , \ \&%i , \ \&%o , \ \&%u , \ \&%X , \ \&%x
+Four byte default, one, two and four byte counts supported.
+.TP
+.BR \&%E , \ \&%e , \ \&%f , \ \&%G , \ \&%g
+Eight byte default, four byte counts supported.
+.PP
+The amount of data interpreted by each format string is the sum of the data
+required by each format unit, which is the iteration count times the byte
+count, or the iteration count times the number of bytes required by the
+format if the byte count is not specified.
+.PP
+The input is manipulated in
+.IR blocks ,
+where a block is defined as the largest amount of data specified by any
+format string. Format strings interpreting less than an input block's worth
+of data, whose last format unit both interprets some number of bytes and does
+not have a specified iteration count, have the iteration count incremented
+until the entire input block has been processed or there is not enough data
+remaining in the block to satisfy the format string.
+.PP
+If, either as a result of user specification or
+.B hexdump
+modifying the iteration count as described above, an iteration count is
+greater than one, no trailing whitespace characters are output during the
+last iteration.
+.PP
+It is an error to specify a byte count as well as multiple conversion
+characters or strings unless all but one of the conversion characters or
+strings is
+.B \&_a
+or
+.BR \&_A .
+.PP
+If, as a result of the specification of the
+.B \-n
+option or end-of-file being reached, input data only partially satisfies a
+format string, the input block is zero-padded sufficiently to display all
+available data (i.e., any format units overlapping the end of data will
+display some number of the zero bytes).
+.PP
+Further output by such format strings is replaced by an equivalent number of
+spaces. An equivalent number of spaces is defined as the number of spaces
+output by an
+.B s
+conversion character with the same field width and precision as the original
+conversion character or conversion string but with any
+.RB ' \&+ ',
+\' \',
+.RB ' \&# '
+conversion flag characters removed, and referencing a NULL string.
+.PP
+If no format strings are specified, the default display is very similar to
+the \fB\-x\fR output format (the \fB\-x\fR option causes more space to be
+used between format units than in the default output).
+.SH EXIT STATUS
+.B hexdump
+exits 0 on success and >0 if an error occurred.
+.SH CONFORMING TO
+The
+.B hexdump
+utility is expected to be IEEE Std 1003.2 ("POSIX.2") compatible.
+.SH EXAMPLES
+Display the input in perusal format:
+.nf
+ "%06.6_ao " 12/1 "%3_u "
+ "\et\et" "%_p "
+ "\en"
+.fi
+.PP
+Implement the \-x option:
+.nf
+ "%07.7_Ax\en"
+ "%07.7_ax " 8/2 "%04x " "\en"
+.fi
+.PP
+MBR Boot Signature example: Highlight the addresses cyan and the bytes at
+offsets 510 and 511 green if their value is 0xAA55, red otherwise.
+.nf
+ "%07.7_Ax_L[cyan]\en"
+ "%07.7_ax_L[cyan] " 8/2 " %04x_L[green:0xAA55@510-511,!red:0xAA55@510-511] " "\en"
+.fi
+.SH COLORS
+Implicit coloring can be disabled by an empty file \fI/etc/terminal-colors.d/hexdump.disable\fR.
+
+See
+.BR terminal-colors.d (5)
+for more details about colorization configuration.
+.SH AVAILABILITY
+The hexdump command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/text-utils/hexdump.c b/text-utils/hexdump.c
new file mode 100644
index 0000000..4068fb8
--- /dev/null
+++ b/text-utils/hexdump.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 1989 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+ /* 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <err.h>
+#include <limits.h>
+#include <getopt.h>
+#include "hexdump.h"
+
+#include "list.h"
+#include "nls.h"
+#include "c.h"
+#include "colors.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "closestream.h"
+
+void hex_free(struct hexdump *);
+
+int
+parse_args(int argc, char **argv, struct hexdump *hex)
+{
+ int ch;
+ int colormode = UL_COLORMODE_UNDEF;
+ char *hex_offt = "\"%07.7_Ax\n\"";
+
+
+ static const struct option longopts[] = {
+ {"one-byte-octal", no_argument, NULL, 'b'},
+ {"one-byte-char", no_argument, NULL, 'c'},
+ {"canonical", no_argument, NULL, 'C'},
+ {"two-bytes-decimal", no_argument, NULL, 'd'},
+ {"two-bytes-octal", no_argument, NULL, 'o'},
+ {"two-bytes-hex", no_argument, NULL, 'x'},
+ {"format", required_argument, NULL, 'e'},
+ {"format-file", required_argument, NULL, 'f'},
+ {"color", optional_argument, NULL, 'L'},
+ {"length", required_argument, NULL, 'n'},
+ {"skip", required_argument, NULL, 's'},
+ {"no-squeezing", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ if (!strcmp(program_invocation_short_name, "hd")) {
+ /* Canonical format */
+ add_fmt("\"%08.8_Ax\n\"", hex);
+ add_fmt("\"%08.8_ax \" 8/1 \"%02x \" \" \" 8/1 \"%02x \" ", hex);
+ add_fmt("\" |\" 16/1 \"%_p\" \"|\\n\"", hex);
+ }
+
+ while ((ch = getopt_long(argc, argv, "bcCde:f:L::n:os:vxhV", longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'b':
+ add_fmt(hex_offt, hex);
+ add_fmt("\"%07.7_ax \" 16/1 \"%03o \" \"\\n\"", hex);
+ break;
+ case 'c':
+ add_fmt(hex_offt, hex);
+ add_fmt("\"%07.7_ax \" 16/1 \"%3_c \" \"\\n\"", hex);
+ break;
+ case 'C':
+ add_fmt("\"%08.8_Ax\n\"", hex);
+ add_fmt("\"%08.8_ax \" 8/1 \"%02x \" \" \" 8/1 \"%02x \" ", hex);
+ add_fmt("\" |\" 16/1 \"%_p\" \"|\\n\"", hex);
+ break;
+ case 'd':
+ add_fmt(hex_offt, hex);
+ add_fmt("\"%07.7_ax \" 8/2 \" %05u \" \"\\n\"", hex);
+ break;
+ case 'e':
+ add_fmt(optarg, hex);
+ break;
+ case 'f':
+ addfile(optarg, hex);
+ break;
+ case 'L':
+ colormode = UL_COLORMODE_AUTO;
+ if (optarg)
+ colormode = colormode_or_err(optarg,
+ _("unsupported color mode"));
+ break;
+ case 'n':
+ hex->length = strtosize_or_err(optarg, _("failed to parse length"));
+ break;
+ case 'o':
+ add_fmt(hex_offt, hex);
+ add_fmt("\"%07.7_ax \" 8/2 \" %06o \" \"\\n\"", hex);
+ break;
+ case 's':
+ hex->skip = strtosize_or_err(optarg, _("failed to parse offset"));
+ break;
+ case 'v':
+ vflag = ALL;
+ break;
+ case 'x':
+ add_fmt(hex_offt, hex);
+ add_fmt("\"%07.7_ax \" 8/2 \" %04x \" \"\\n\"", hex);
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (list_empty(&hex->fshead)) {
+ add_fmt(hex_offt, hex);
+ add_fmt("\"%07.7_ax \" 8/2 \"%04x \" \"\\n\"", hex);
+ }
+ colors_init (colormode, "hexdump");
+ return optind;
+}
+
+void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] <file>...\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Display file contents in hexadecimal, decimal, octal, or ascii.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -b, --one-byte-octal one-byte octal display\n"), out);
+ fputs(_(" -c, --one-byte-char one-byte character display\n"), out);
+ fputs(_(" -C, --canonical canonical hex+ASCII display\n"), out);
+ fputs(_(" -d, --two-bytes-decimal two-byte decimal display\n"), out);
+ fputs(_(" -o, --two-bytes-octal two-byte octal display\n"), out);
+ fputs(_(" -x, --two-bytes-hex two-byte hexadecimal display\n"), out);
+ fputs(_(" -L, --color[=<mode>] interpret color formatting specifiers\n"), out);
+ fprintf(out,
+ " %s\n", USAGE_COLORS_DEFAULT);
+ fputs(_(" -e, --format <format> format string to be used for displaying data\n"), out);
+ fputs(_(" -f, --format-file <file> file that contains format strings\n"), out);
+ fputs(_(" -n, --length <length> interpret only length bytes of input\n"), out);
+ fputs(_(" -s, --skip <offset> skip offset bytes from the beginning\n"), out);
+ fputs(_(" -v, --no-squeezing output identical lines\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(27));
+
+ fputs(USAGE_ARGUMENTS, out);
+ printf(USAGE_ARG_SIZE(_("<length> and <offset>")));
+
+ printf(USAGE_MAN_TAIL("hexdump(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ struct list_head *p;
+ struct hexdump_fs *tfs;
+ int ret;
+
+ struct hexdump *hex = xcalloc(1, sizeof (struct hexdump));
+ hex->length = -1;
+ INIT_LIST_HEAD(&hex->fshead);
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ argv += parse_args(argc, argv, hex);
+
+ /* figure out the data block size */
+ hex->blocksize = 0;
+ list_for_each(p, &hex->fshead) {
+ tfs = list_entry(p, struct hexdump_fs, fslist);
+ if ((tfs->bcnt = block_size(tfs)) > hex->blocksize)
+ hex->blocksize = tfs->bcnt;
+ }
+
+ /* rewrite the rules, do syntax checking */
+ list_for_each(p, &hex->fshead)
+ rewrite_rules(list_entry(p, struct hexdump_fs, fslist), hex);
+
+ next(argv, hex);
+ display(hex);
+
+ ret = hex->exitval;
+ hex_free(hex);
+
+ return ret;
+}
+
+void hex_free(struct hexdump *hex)
+{
+ struct list_head *p, *pn, *q, *qn, *r, *rn, *s, *sn;
+ struct hexdump_fs *fs;
+ struct hexdump_fu *fu;
+ struct hexdump_pr *pr;
+ struct hexdump_clr *clr;
+
+ list_for_each_safe(p, pn, &hex->fshead) {
+ fs = list_entry(p, struct hexdump_fs, fslist);
+ list_for_each_safe(q, qn, &fs->fulist) {
+ fu = list_entry(q, struct hexdump_fu, fulist);
+ list_for_each_safe(r, rn, &fu->prlist) {
+ pr = list_entry(r, struct hexdump_pr, prlist);
+ if (pr->colorlist) {
+ list_for_each_safe(s, sn, pr->colorlist) {
+ clr = list_entry (s, struct hexdump_clr, colorlist);
+ free(clr->str);
+ free(clr);
+ }
+ }
+ free(pr->fmt);
+ free(pr);
+ }
+ free(fu->fmt);
+ free(fu);
+ }
+ free(fs);
+ }
+ free (hex);
+}
diff --git a/text-utils/hexdump.h b/text-utils/hexdump.h
new file mode 100644
index 0000000..3233985
--- /dev/null
+++ b/text-utils/hexdump.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 1989 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ *
+ * @(#)hexdump.h 5.4 (Berkeley) 6/1/90
+ */
+#ifndef UTIL_LINUX_HEXDUMP_H
+#define UTIL_LINUX_HEXDUMP_H
+
+#include "c.h"
+#include "list.h"
+
+struct hexdump_clr {
+ struct list_head colorlist; /* next color unit */
+ const char *fmt; /* the color, UL_COLOR_* */
+ off_t offt; /* offset where unit is valid... */
+ int range; /* ... and it's range */
+ int val; /* value ... */
+ char *str; /* ... or string to match */
+ int invert; /* invert condition? */
+};
+
+struct hexdump_pr {
+ struct list_head prlist; /* next print unit */
+#define F_ADDRESS 0x001 /* print offset */
+#define F_BPAD 0x002 /* blank pad */
+#define F_C 0x004 /* %_c */
+#define F_CHAR 0x008 /* %c */
+#define F_DBL 0x010 /* %[EefGf] */
+#define F_INT 0x020 /* %[di] */
+#define F_P 0x040 /* %_p */
+#define F_STR 0x080 /* %s */
+#define F_U 0x100 /* %_u */
+#define F_UINT 0x200 /* %[ouXx] */
+#define F_TEXT 0x400 /* no conversions */
+ unsigned int flags; /* flag values */
+ int bcnt; /* byte count */
+ char *cchar; /* conversion character */
+ struct list_head *colorlist; /* color settings */
+ char *fmt; /* printf format */
+ char *nospace; /* no whitespace version */
+};
+
+struct hexdump_fu {
+ struct list_head fulist; /* next format unit */
+ struct list_head prlist; /* next print unit */
+#define F_IGNORE 0x01 /* %_A */
+#define F_SETREP 0x02 /* rep count set, not default */
+ unsigned int flags; /* flag values */
+ int reps; /* repetition count */
+ int bcnt; /* byte count */
+ char *fmt; /* format string */
+};
+
+struct hexdump_fs { /* format strings */
+ struct list_head fslist; /* linked list of format strings */
+ struct list_head fulist; /* linked list of format units */
+ int bcnt;
+};
+
+struct hexdump {
+ struct list_head fshead; /* head of format strings */
+ ssize_t blocksize; /* data block size */
+ int exitval; /* final exit value */
+ ssize_t length; /* max bytes to read */
+ off_t skip; /* bytes to skip */
+};
+
+extern struct hexdump_fu *endfu;
+
+enum _vflag { ALL, DUP, FIRST, WAIT }; /* -v values */
+extern enum _vflag vflag;
+
+int block_size(struct hexdump_fs *);
+void add_fmt(const char *, struct hexdump *);
+void rewrite_rules(struct hexdump_fs *, struct hexdump *);
+void addfile(char *, struct hexdump *);
+void display(struct hexdump *);
+void __attribute__((__noreturn__)) usage(void);
+void conv_c(struct hexdump_pr *, u_char *);
+void conv_u(struct hexdump_pr *, u_char *);
+int next(char **, struct hexdump *);
+int parse_args(int, char **, struct hexdump *);
+
+#endif /* UTIL_LINUX_HEXDUMP_H */
diff --git a/text-utils/line.1 b/text-utils/line.1
new file mode 100644
index 0000000..a766791
--- /dev/null
+++ b/text-utils/line.1
@@ -0,0 +1,17 @@
+.\" This page is in the public domain
+.TH LINE 1 "July 2002" "util-linux" "User Commands"
+.SH NAME
+line \- read one line
+.SH SYNOPSIS
+.B line
+.SH DESCRIPTION
+The utility
+.B line
+copies one line (up to a newline) from standard input to standard output.
+It always prints at least a newline and returns an exit status of 1
+on EOF or read error.
+.SH SEE ALSO
+.BR read (1p)
+.SH AVAILABILITY
+The line command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/text-utils/line.c b/text-utils/line.c
new file mode 100644
index 0000000..e894076
--- /dev/null
+++ b/text-utils/line.c
@@ -0,0 +1,83 @@
+/*
+ * line - read one line
+ *
+ * Gunnar Ritter, Freiburg i. Br., Germany, December 2000.
+ *
+ * Public Domain.
+ */
+
+/*
+ * This command is deprecated. The utility is in maintenance mode,
+ * meaning we keep them in source tree for backward compatibility
+ * only. Do not waste time making this command better, unless the
+ * fix is about security or other very critical issue.
+ *
+ * See Documentation/deprecated.txt for more information.
+ */
+
+#include <getopt.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "widechar.h"
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Read one line.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("line(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ wint_t c;
+ int opt;
+ int status = EXIT_SUCCESS;
+
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((opt = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (opt) {
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ setvbuf(stdin, NULL, _IONBF, 0);
+ for (;;) {
+ c = getwchar();
+ if (c == WEOF) {
+ status = EXIT_FAILURE;
+ break;
+ }
+ if (c == '\n')
+ break;
+ putwchar(c);
+ }
+ putwchar(L'\n');
+
+ return status;
+}
diff --git a/text-utils/more.1 b/text-utils/more.1
new file mode 100644
index 0000000..046c2c4
--- /dev/null
+++ b/text-utils/more.1
@@ -0,0 +1,258 @@
+.\" Copyright (c) 1988, 1990 The Regents of the University of California.
+.\" Copyright (c) 1988 Mark Nudleman
+.\" 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)more.1 5.15 (Berkeley) 7/29/91
+.\"
+.\" Copyright (c) 1992 Rik Faith (faith@cs.unc.edu)
+.\"
+.TH MORE "1" "March 2020" "util-linux" "User Commands"
+.SH NAME
+more \- file perusal filter for crt viewing
+.SH SYNOPSIS
+.B more
+[options]
+.IR file ...
+.SH DESCRIPTION
+.B more
+is a filter for paging through text one screenful at a time. This version is
+especially primitive. Users should realize that
+.BR less (1)
+provides
+.BR more (1)
+emulation plus extensive enhancements.
+.SH OPTIONS
+Options are also taken from the environment variable
+.B MORE
+(make sure to precede them with a dash
+.RB ( \- ))
+but command-line options will override those.
+.TP
+.BR \-d , " \-\-silent"
+Prompt with "[Press space to continue, 'q' to quit.]",
+and display "[Press 'h' for instructions.]" instead of ringing
+the bell when an illegal key is pressed.
+.TP
+.BR \-l , " \-\-logical"
+Do not pause after any line containing a
+.B \&^L
+(form feed).
+.TP
+.BR \-f , " \-\-no\-pause"
+Count logical lines, rather than screen lines (i.e., long lines are not folded).
+.TP
+.BR \-p , " \-\-print\-over"
+Do not scroll. Instead, clear the whole screen and then display the text.
+Notice that this option is switched on automatically if the executable is
+named
+.BR page .
+.TP
+.BR \-c , " \-\-clean\-print"
+Do not scroll. Instead, paint each screen from the top, clearing the
+remainder of each line as it is displayed.
+.TP
+.BR \-s , " \-\-squeeze"
+Squeeze multiple blank lines into one.
+.TP
+.BR \-u , " \-\-plain"
+Suppress underlining. This option is silently ignored as backwards
+compatibility.
+.TP
+\fB\-n\fR, \fB\-\-lines \fInumber\fR
+Specify the
+.I number
+of lines per screenful. The
+.I number
+argument is a positive decimal integer. The
+.B \-\-lines
+option shall override any values obtained from any other source, such as
+number of lines reported by terminal.
+.TP
+.BI \- number
+A numeric option means the same as
+.B \-\-lines
+option argument.
+.TP
+.BI + number
+Start displaying each file at line
+.IR number .
+.TP
+.BI +/ string
+The
+.I string
+to be searched in each file before starting to display it.
+.TP
+\fB\-\-help\fR
+Display help text and exit.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.SH COMMANDS
+Interactive commands for
+.B more
+are based on
+.BR vi (1).
+Some commands may be preceded by a decimal number, called k in the
+descriptions below. In the following descriptions,
+.B ^X
+means
+.BR control-X .
+.PP
+.RS
+.PD 1
+.TP 10
+.BR h \ or \ ?
+Help; display a summary of these commands. If you forget all other
+commands, remember this one.
+.TP
+.B SPACE
+Display next k lines of text. Defaults to current screen size.
+.TP
+.B z
+Display next k lines of text. Defaults to current screen size. Argument
+becomes new default.
+.TP
+.B RETURN
+Display next k lines of text. Defaults to 1. Argument becomes new default.
+.TP
+.BR d \ or \ \&^D
+Scroll k lines. Default is current scroll size, initially 11. Argument
+becomes new default.
+.TP
+.BR q \ or \ Q \ or \ INTERRUPT
+Exit.
+.TP
+.B s
+Skip forward k lines of text. Defaults to 1.
+.TP
+.B f
+Skip forward k screenfuls of text. Defaults to 1.
+.TP
+.BR b \ or \ \&^B
+Skip backwards k screenfuls of text. Defaults to 1. Only works with files,
+not pipes.
+.TP
+.B '
+Go to the place where the last search started.
+.TP
+.B =
+Display current line number.
+.TP
+.B \&/pattern
+Search for kth occurrence of regular expression. Defaults to 1.
+.TP
+.B n
+Search for kth occurrence of last regular expression. Defaults to 1.
+.TP
+.BR !command \ or \ :!command
+Execute
+.I command
+in a subshell.
+.TP
+.B v
+Start up an editor at current line. The editor is taken from the environment
+variable
+.B VISUAL
+if defined, or
+.B EDITOR
+if
+.B VISUAL
+is not defined, or defaults
+to
+.B vi
+if neither
+.B VISUAL
+nor
+.B EDITOR
+is defined.
+.TP
+.B \&^L
+Redraw screen.
+.TP
+.B :n
+Go to kth next file. Defaults to 1.
+.TP
+.B :p
+Go to kth previous file. Defaults to 1.
+.TP
+.B :f
+Display current file name and line number.
+.TP
+.B \&.
+Repeat previous command.
+.RE
+.SH ENVIRONMENT
+The
+.B more
+command respects the following environment variables, if they exist:
+.TP
+.B MORE
+This variable may be set with favored options to
+.BR more .
+.TP
+.B SHELL
+Current shell in use (normally set by the shell at login time).
+.TP
+.B TERM
+The terminal type used by \fBmore\fR to get the terminal
+characteristics necessary to manipulate the screen.
+.TP
+.B VISUAL
+The editor the user prefers. Invoked when command key
+.I v
+is pressed.
+.TP
+.B EDITOR
+The editor of choice when
+.B VISUAL
+is not specified.
+.SH HISTORY
+The
+.B more
+command appeared in 3.0BSD. This man page documents
+.B more
+version 5.19 (Berkeley 6/29/88), which is currently in use in the Linux
+community. Documentation was produced using several other versions of the
+man page, and extensive inspection of the source code.
+.SH AUTHORS
+Eric Shienbrood, UC Berkeley
+.br
+Modified by Geoff Peck, UCB to add underlining, single spacing
+.br
+Modified by John Foderaro, UCB to add \-c and MORE environment variable
+.SH SEE ALSO
+.BR less (1),
+.BR vi (1)
+.SH AVAILABILITY
+The more command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/text-utils/more.c b/text-utils/more.c
new file mode 100644
index 0000000..3855d85
--- /dev/null
+++ b/text-utils/more.c
@@ -0,0 +1,2097 @@
+/*
+ * Copyright (C) 1980 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley. The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/* more.c - General purpose tty output filter and file perusal program
+ *
+ * by Eric Shienbrood, UC Berkeley
+ *
+ * modified by Geoff Peck
+ * UCB to add underlining, single spacing
+ * modified by John Foderaro
+ * UCB to add -c and MORE environment variable
+ * modified by Erik Troan <ewt@redhat.com>
+ * to be more posix and so compile on linux/axp.
+ * modified by Kars de Jong <jongk@cs.utwente.nl>
+ * to use terminfo instead of termcap.
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * added Native Language Support
+ * 1999-03-19 Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * more nls translatable strings
+ * 1999-05-09 aeb
+ * applied a RedHat patch (setjmp->sigsetjmp); without it a second
+ * ^Z would fail.
+ * 1999-05-09 aeb
+ * undone Kars' work, so that more works without libcurses (and
+ * hence can be in /bin with libcurses being in
+ * /usr/lib which may not be mounted). However, when termcap is not
+ * present curses can still be used.
+ * 2010-10-21 Davidlohr Bueso <dave@gnu.org>
+ * modified mem allocation handling for util-linux
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/param.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/ttydefaults.h>
+#include <sys/wait.h>
+#include <regex.h>
+#include <assert.h>
+#include <poll.h>
+#include <sys/signalfd.h>
+#include <paths.h>
+#include <getopt.h>
+
+#if defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+#elif defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+#elif defined(HAVE_TERM_H)
+# include <term.h>
+#endif
+
+#ifdef HAVE_MAGIC
+# include <magic.h>
+#endif
+
+#include "strutils.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "widechar.h"
+#include "closestream.h"
+#include "rpmatch.h"
+#include "env.h"
+
+#ifdef TEST_PROGRAM
+# define NON_INTERACTIVE_MORE 1
+#endif
+
+#define BACKSPACE "\b"
+#define CARAT "^"
+
+#define ARROW_UP "\x1b\x5b\x41"
+#define ARROW_DOWN "\x1b\x5b\x42"
+#define PAGE_UP "\x1b\x5b\x35\x7e"
+#define PAGE_DOWN "\x1b\x5b\x36\x7e"
+
+#define MIN_LINE_SZ 256 /* minimal line_buf buffer size */
+#define ESC '\033'
+#define SCROLL_LEN 11
+#define LINES_PER_PAGE 24
+#define NUM_COLUMNS 80
+#define TERMINAL_BUF 4096
+#define INIT_BUF 80
+#define COMMAND_BUF 200
+#define REGERR_BUF NUM_COLUMNS
+
+#define TERM_AUTO_RIGHT_MARGIN "am"
+#define TERM_BACKSPACE "cub1"
+#define TERM_CEOL "xhp"
+#define TERM_CLEAR "clear"
+#define TERM_CLEAR_TO_LINE_END "el"
+#define TERM_CLEAR_TO_SCREEN_END "ed"
+#define TERM_COLS "cols"
+#define TERM_CURSOR_ADDRESS "cup"
+#define TERM_EAT_NEW_LINE "xenl"
+#define TERM_EXIT_STANDARD_MODE "rmso"
+#define TERM_HARD_COPY "hc"
+#define TERM_HOME "home"
+#define TERM_LINE_DOWN "cud1"
+#define TERM_LINES "lines"
+#define TERM_OVER_STRIKE "os"
+#define TERM_STANDARD_MODE "smso"
+#define TERM_STD_MODE_GLITCH "xmc"
+
+/* Used in read_command() */
+typedef enum {
+ more_kc_unknown_command,
+ more_kc_colon,
+ more_kc_repeat_previous,
+ more_kc_backwards,
+ more_kc_jump_lines_per_screen,
+ more_kc_set_lines_per_screen,
+ more_kc_set_scroll_len,
+ more_kc_quit,
+ more_kc_skip_forward,
+ more_kc_next_line,
+ more_kc_clear_screen,
+ more_kc_previous_search_match,
+ more_kc_display_line,
+ more_kc_display_file_and_line,
+ more_kc_repeat_search,
+ more_kc_search,
+ more_kc_run_shell,
+ more_kc_help,
+ more_kc_next_file,
+ more_kc_previous_file,
+ more_kc_run_editor
+} more_key_commands;
+struct number_command {
+ unsigned int number;
+ more_key_commands key;
+};
+
+struct more_control {
+ struct termios output_tty; /* output terminal */
+ struct termios original_tty; /* original terminal settings */
+ FILE *current_file; /* currently open input file */
+ off_t file_position; /* file position */
+ off_t file_size; /* file size */
+ int argv_position; /* argv[] position */
+ int lines_per_screen; /* screen size in lines */
+ int d_scroll_len; /* number of lines scrolled by 'd' */
+ int prompt_len; /* message prompt length */
+ int current_line; /* line we are currently at */
+ int next_jump; /* number of lines to skip ahead */
+ char **file_names; /* The list of file names */
+ int num_files; /* Number of files left to process */
+ char *shell; /* name of the shell to use */
+ int sigfd; /* signalfd() file descriptor */
+ sigset_t sigset; /* signal operations */
+ char *line_buf; /* line buffer */
+ size_t line_sz; /* size of line_buf buffer */
+ int lines_per_page; /* lines per page */
+ char *clear; /* clear screen */
+ char *erase_line; /* erase line */
+ char *enter_std; /* enter standout mode */
+ char *exit_std; /* exit standout mode */
+ char *backspace_ch; /* backspace character */
+ char *go_home; /* go to home */
+ char *move_line_down; /* move line down */
+ char *clear_rest; /* clear rest of screen */
+ int num_columns; /* number of columns */
+ char *next_search; /* file beginning search string */
+ char *previous_search; /* previous search() buf[] item */
+ struct {
+ off_t row_num; /* row file position */
+ long line_num; /* line number */
+ } context,
+ screen_start;
+ unsigned int leading_number; /* number in front of key command */
+ struct number_command previous_command; /* previous key command */
+ char *shell_line; /* line to execute in subshell */
+#ifdef HAVE_MAGIC
+ magic_t magic; /* libmagic database entries */
+#endif
+ unsigned int
+ bad_stdout:1, /* true if overwriting does not turn off standout */
+ catch_suspend:1, /* we should catch the SIGTSTP signal */
+ clear_line_ends:1, /* do not scroll, paint each screen from the top */
+ clear_first:1, /* is first character in file \f */
+ dumb_tty:1, /* is terminal type known */
+ eat_newline:1, /* is newline ignored after 80 cols */
+ erase_input_ok:1, /* is erase input supported */
+ erase_previous_ok:1, /* is erase previous supported */
+ first_file:1, /* is the input file the first in list */
+ fold_long_lines:1, /* fold long lines */
+ hard_tabs:1, /* print spaces instead of '\t' */
+ hard_tty:1, /* is this hard copy terminal (a printer or such) */
+ leading_colon:1, /* key command has leading ':' character */
+ is_paused:1, /* is output paused */
+ no_quit_dialog:1, /* suppress quit dialog */
+ no_scroll:1, /* do not scroll, clear the screen and then display text */
+ no_tty_in:1, /* is input in interactive mode */
+ no_tty_out:1, /* is output in interactive mode */
+ print_banner:1, /* print file name banner */
+ reading_num:1, /* are we reading leading_number */
+ report_errors:1, /* is an error reported */
+ search_at_start:1, /* search pattern defined at start up */
+ search_called:1, /* previous more command was a search */
+ squeeze_spaces:1, /* suppress white space */
+ stdout_glitch:1, /* terminal has standout mode glitch */
+ stop_after_formfeed:1, /* stop after form feeds */
+ suppress_bell:1, /* suppress bell */
+ wrap_margin:1; /* set if automargins */
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ printf("%s", USAGE_HEADER);
+ printf(_(" %s [options] <file>...\n"), program_invocation_short_name);
+
+ printf("%s", USAGE_SEPARATOR);
+ printf("%s\n", _("A file perusal filter for CRT viewing."));
+
+ printf("%s", USAGE_OPTIONS);
+ printf("%s\n", _(" -d, --silent display help instead of ringing bell"));
+ printf("%s\n", _(" -f, --logical count logical rather than screen lines"));
+ printf("%s\n", _(" -l, --no-pause suppress pause after form feed"));
+ printf("%s\n", _(" -c, --print-over do not scroll, display text and clean line ends"));
+ printf("%s\n", _(" -p, --clean-print do not scroll, clean screen and display text"));
+ printf("%s\n", _(" -s, --squeeze squeeze multiple blank lines into one"));
+ printf("%s\n", _(" -u, --plain suppress underlining and bold"));
+ printf("%s\n", _(" -n, --lines <number> the number of lines per screenful"));
+ printf("%s\n", _(" -<number> same as --lines"));
+ printf("%s\n", _(" +<number> display file beginning from line number"));
+ printf("%s\n", _(" +/<pattern> display file beginning from pattern match"));
+ printf("%s", USAGE_SEPARATOR);
+ printf(USAGE_HELP_OPTIONS(23));
+ printf(USAGE_MAN_TAIL("more(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static void argscan(struct more_control *ctl, int as_argc, char **as_argv)
+{
+ int c, opt;
+ static const struct option longopts[] = {
+ { "silent", no_argument, NULL, 'd' },
+ { "logical", no_argument, NULL, 'f' },
+ { "no-pause", no_argument, NULL, 'l' },
+ { "print-over", no_argument, NULL, 'c' },
+ { "clean-print", no_argument, NULL, 'p' },
+ { "squeeze", no_argument, NULL, 's' },
+ { "plain", no_argument, NULL, 'u' },
+ { "lines", required_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ /* Take care of number option and +args. */
+ for (opt = 0; opt < as_argc; opt++) {
+ int move = 0;
+
+ if (as_argv[opt][0] == '-' && isdigit_string(as_argv[opt] + 1)) {
+ ctl->lines_per_screen =
+ strtos16_or_err(as_argv[opt], _("failed to parse number"));
+ ctl->lines_per_screen = abs(ctl->lines_per_screen);
+ move = 1;
+ } else if (as_argv[opt][0] == '+') {
+ if (isdigit_string(as_argv[opt] + 1)) {
+ ctl->next_jump = strtos32_or_err(as_argv[opt],
+ _("failed to parse number")) - 1;
+ move = 1;
+ } else if (as_argv[opt][1] == '/') {
+ free(ctl->next_search);
+ ctl->next_search = xstrdup(as_argv[opt] + 2);
+ ctl->search_at_start = 1;
+ move = 1;
+ }
+ }
+ if (move) {
+ as_argc = remote_entry(as_argv, opt, as_argc);
+ opt--;
+ }
+ }
+
+ while ((c = getopt_long(as_argc, as_argv, "dflcpsun:eVh", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ ctl->suppress_bell = 1;
+ break;
+ case 'l':
+ ctl->stop_after_formfeed = 0;
+ break;
+ case 'f':
+ ctl->fold_long_lines = 0;
+ break;
+ case 'p':
+ ctl->no_scroll = 1;
+ break;
+ case 'c':
+ ctl->clear_line_ends = 1;
+ break;
+ case 's':
+ ctl->squeeze_spaces = 1;
+ break;
+ case 'u':
+ break;
+ case 'n':
+ ctl->lines_per_screen = strtou16_or_err(optarg, _("argument error"));
+ break;
+ case 'e': /* ignored silently to be posix compliant */
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ break;
+ }
+ }
+ ctl->num_files = as_argc - optind;
+ ctl->file_names = as_argv + optind;
+}
+
+static void env_argscan(struct more_control *ctl, const char *s)
+{
+ char **env_argv;
+ int env_argc = 1;
+ int size = 8;
+ const char delim[] = { ' ', '\n', '\t', '\0' };
+ char *str = xstrdup(s);
+ char *key = NULL, *tok;
+
+ env_argv = xmalloc(sizeof(char *) * size);
+ env_argv[0] = _("MORE environment variable"); /* program name */
+ for (tok = strtok_r(str, delim, &key); tok; tok = strtok_r(NULL, delim, &key)) {
+ env_argv[env_argc++] = tok;
+ if (size < env_argc) {
+ size *= 2;
+ env_argv = xrealloc(env_argv, sizeof(char *) * size);
+ }
+ }
+
+ argscan(ctl, env_argc, env_argv);
+ /* Reset optind, command line parsing needs this. */
+ optind = 0;
+ free(str);
+ free(env_argv);
+}
+
+static void more_fseek(struct more_control *ctl, off_t pos)
+{
+ ctl->file_position = pos;
+ fseeko(ctl->current_file, pos, SEEK_SET);
+}
+
+static int more_getc(struct more_control *ctl)
+{
+ int ret = getc(ctl->current_file);
+ ctl->file_position = ftello(ctl->current_file);
+ return ret;
+}
+
+static int more_ungetc(struct more_control *ctl, int c)
+{
+ int ret = ungetc(c, ctl->current_file);
+ ctl->file_position = ftello(ctl->current_file);
+ return ret;
+}
+
+static void print_separator(const int c, int n)
+{
+ while (n--)
+ putchar(c);
+ putchar('\n');
+}
+
+/* check_magic -- check for file magic numbers. */
+static int check_magic(struct more_control *ctl, char *fs)
+{
+#ifdef HAVE_MAGIC
+ const int fd = fileno(ctl->current_file);
+ const char *mime_encoding = magic_descriptor(ctl->magic, fd);
+ const char *magic_error_msg = magic_error(ctl->magic);
+ struct stat st;
+
+ if (magic_error_msg) {
+ printf(_("magic failed: %s\n"), magic_error_msg);
+ return 0;
+ }
+ if (fstat(fd, &st)) {
+ warn(_("cannot stat %s"), fs);
+ return 1;
+ }
+ if (st.st_size == 0) {
+ /* libmagic tells an empty file has binary encoding */
+ return 0;
+ }
+
+ if (!mime_encoding || !(strcmp("binary", mime_encoding))) {
+ printf(_("\n******** %s: Not a text file ********\n\n"), fs);
+ return 1;
+ }
+#else
+ signed char twobytes[2];
+
+ /* don't try to look ahead if the input is unseekable */
+ if (fseek(ctl->current_file, 0L, SEEK_SET))
+ return 0;
+
+ if (fread(twobytes, 2, 1, ctl->current_file) == 1) {
+ switch (twobytes[0] + (twobytes[1] << 8)) {
+ case 0407: /* a.out obj */
+ case 0410: /* a.out exec */
+ case 0413: /* a.out demand exec */
+ case 0405:
+ case 0411:
+ case 0177545:
+ case 0x457f: /* simple ELF detection */
+ printf(_("\n******** %s: Not a text file ********\n\n"),
+ fs);
+ return 1;
+ }
+ }
+ fseek(ctl->current_file, 0L, SEEK_SET); /* rewind() not necessary */
+#endif
+ return 0;
+}
+
+/* Check whether the file named by fs is an ASCII file which the user may
+ * access. If it is, return the opened file. Otherwise return NULL. */
+static void checkf(struct more_control *ctl, char *fs)
+{
+ struct stat st;
+ int c;
+
+ ctl->current_line = 0;
+ ctl->file_position = 0;
+ fflush(NULL);
+ if (((ctl->current_file = fopen(fs, "r")) == NULL) ||
+ (fstat(fileno(ctl->current_file), &st) != 0)) {
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ warn(_("cannot open %s"), fs);
+ return;
+ }
+#ifndef HAVE_MAGIC
+ if ((st.st_mode & S_IFMT) == S_IFDIR) {
+ printf(_("\n*** %s: directory ***\n\n"), fs);
+ ctl->current_file = NULL;
+ return;
+ }
+#endif
+ if (check_magic(ctl, fs)) {
+ fclose(ctl->current_file);
+ ctl->current_file = NULL;
+ return;
+ }
+ fcntl(fileno(ctl->current_file), F_SETFD, FD_CLOEXEC);
+ c = more_getc(ctl);
+ ctl->clear_first = (c == '\f');
+ more_ungetc(ctl, c);
+ if ((ctl->file_size = st.st_size) == 0)
+ ctl->file_size = ~((off_t)0);
+}
+
+static void prepare_line_buffer(struct more_control *ctl)
+{
+ size_t sz = ctl->num_columns * 4;
+
+ if (ctl->line_sz >= sz)
+ return;
+
+ if (sz < MIN_LINE_SZ)
+ sz = MIN_LINE_SZ;
+
+ /* alloc sz and extra space for \n\0 */
+ ctl->line_buf = xrealloc(ctl->line_buf, sz + 2);
+ ctl->line_sz = sz;
+}
+
+/* Get a logical line */
+static int get_line(struct more_control *ctl, int *length)
+{
+ int c;
+ char *p;
+ int column;
+ static int column_wrap;
+
+#ifdef HAVE_WIDECHAR
+ size_t i;
+ wchar_t wc;
+ int wc_width;
+ mbstate_t state, state_bak; /* Current status of the stream. */
+ char mbc[MB_LEN_MAX]; /* Buffer for one multibyte char. */
+ size_t mblength; /* Byte length of multibyte char. */
+ size_t mbc_pos = 0; /* Position of the MBC. */
+ int use_mbc_buffer_flag = 0; /* If 1, mbc has data. */
+ int break_flag = 0; /* If 1, exit while(). */
+ off_t file_position_bak = ctl->file_position;
+
+ memset(&state, '\0', sizeof(mbstate_t));
+#endif
+
+ p = ctl->line_buf;
+ column = 0;
+ c = more_getc(ctl);
+ if (column_wrap && c == '\n') {
+ ctl->current_line++;
+ c = more_getc(ctl);
+ }
+ while (p < &ctl->line_buf[ctl->line_sz - 1]) {
+#ifdef HAVE_WIDECHAR
+ if (ctl->fold_long_lines && use_mbc_buffer_flag && MB_CUR_MAX > 1) {
+ use_mbc_buffer_flag = 0;
+ state_bak = state;
+ mbc[mbc_pos++] = c;
+ process_mbc:
+ mblength = mbrtowc(&wc, mbc, mbc_pos, &state);
+
+ switch (mblength) {
+ case (size_t)-2: /* Incomplete multibyte character. */
+ use_mbc_buffer_flag = 1;
+ state = state_bak;
+ break;
+
+ case (size_t)-1: /* Invalid as a multibyte character. */
+ *p++ = mbc[0];
+ state = state_bak;
+ column++;
+ file_position_bak++;
+ if (column >= ctl->num_columns) {
+ more_fseek(ctl, file_position_bak);
+ } else {
+ memmove(mbc, mbc + 1, --mbc_pos);
+ if (mbc_pos > 0) {
+ mbc[mbc_pos] = '\0';
+ goto process_mbc;
+ }
+ }
+ break;
+
+ default:
+ wc_width = wcwidth(wc);
+ if (column + wc_width > ctl->num_columns) {
+ more_fseek(ctl, file_position_bak);
+ break_flag = 1;
+ } else {
+ for (i = 0; p < &ctl->line_buf[ctl->line_sz - 1] &&
+ i < mbc_pos; i++)
+ *p++ = mbc[i];
+ if (wc_width > 0)
+ column += wc_width;
+ }
+ }
+
+ if (break_flag || column >= ctl->num_columns)
+ break;
+
+ c = more_getc(ctl);
+ continue;
+ }
+#endif /* HAVE_WIDECHAR */
+ if (c == EOF) {
+ if (p > ctl->line_buf) {
+ *p = '\0';
+ *length = p - ctl->line_buf;
+ return column;
+ }
+ *length = p - ctl->line_buf;
+ return EOF;
+ }
+ if (c == '\n') {
+ ctl->current_line++;
+ break;
+ }
+
+ *p++ = c;
+ if (c == '\t') {
+ if (!ctl->hard_tabs || (column < ctl->prompt_len && !ctl->hard_tty)) {
+ if (ctl->hard_tabs && ctl->erase_line && !ctl->dumb_tty) {
+ column = 1 + (column | 7);
+ putp(ctl->erase_line);
+ ctl->prompt_len = 0;
+ } else {
+ for (--p; p < &ctl->line_buf[ctl->line_sz - 1];) {
+ *p++ = ' ';
+ if ((++column & 7) == 0)
+ break;
+ }
+ if (column >= ctl->prompt_len)
+ ctl->prompt_len = 0;
+ }
+ } else
+ column = 1 + (column | 7);
+ } else if (c == '\b' && column > 0) {
+ column--;
+ } else if (c == '\r') {
+ int next = more_getc(ctl);
+ if (next == '\n') {
+ p--;
+ ctl->current_line++;
+ break;
+ }
+ more_ungetc(ctl, c);
+ column = 0;
+ } else if (c == '\f' && ctl->stop_after_formfeed) {
+ p[-1] = '^';
+ *p++ = 'L';
+ column += 2;
+ ctl->is_paused = 1;
+ } else if (c == EOF) {
+ *length = p - ctl->line_buf;
+ return column;
+ } else {
+#ifdef HAVE_WIDECHAR
+ if (ctl->fold_long_lines && MB_CUR_MAX > 1) {
+ memset(mbc, '\0', MB_LEN_MAX);
+ mbc_pos = 0;
+ mbc[mbc_pos++] = c;
+ state_bak = state;
+
+ mblength = mbrtowc(&wc, mbc, mbc_pos, &state);
+ /* The value of mblength is always less than 2 here. */
+ switch (mblength) {
+ case (size_t)-2:
+ p--;
+ file_position_bak = ctl->file_position - 1;
+ state = state_bak;
+ use_mbc_buffer_flag = 1;
+ break;
+
+ case (size_t)-1:
+ state = state_bak;
+ column++;
+ break;
+
+ default:
+ wc_width = wcwidth(wc);
+ if (wc_width > 0)
+ column += wc_width;
+ }
+ } else
+#endif /* HAVE_WIDECHAR */
+ {
+ if (isprint(c))
+ column++;
+ }
+ }
+
+ if (column >= ctl->num_columns && ctl->fold_long_lines)
+ break;
+#ifdef HAVE_WIDECHAR
+ if (use_mbc_buffer_flag == 0 && p >= &ctl->line_buf[ctl->line_sz - 1 - 4])
+ /* don't read another char if there is no space for
+ * whole multibyte sequence */
+ break;
+#endif
+ c = more_getc(ctl);
+ }
+ if (column >= ctl->num_columns && ctl->num_columns > 0) {
+ if (!ctl->wrap_margin) {
+ *p++ = '\n';
+ }
+ }
+ column_wrap = column == ctl->num_columns && ctl->fold_long_lines;
+ if (column_wrap && ctl->eat_newline && ctl->wrap_margin) {
+ *p++ = '\n'; /* simulate normal wrap */
+ }
+ *length = p - ctl->line_buf;
+ *p = 0;
+ return column;
+}
+
+/* Erase the rest of the prompt, assuming we are starting at column col. */
+static void erase_to_col(struct more_control *ctl, int col)
+{
+
+ if (ctl->prompt_len == 0)
+ return;
+ if (col == 0 && ctl->clear_line_ends)
+ puts(ctl->erase_line);
+ else if (ctl->hard_tty)
+ putchar('\n');
+ else {
+ if (col == 0)
+ putchar('\r');
+ if (!ctl->dumb_tty && ctl->erase_line)
+ putp(ctl->erase_line);
+ else {
+ printf("%*s", ctl->prompt_len - col, "");
+ if (col == 0)
+ putchar('\r');
+ }
+ }
+ ctl->prompt_len = col;
+}
+
+static void output_prompt(struct more_control *ctl, char *filename)
+{
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ else if (ctl->prompt_len > 0)
+ erase_to_col(ctl, 0);
+ if (!ctl->hard_tty) {
+ ctl->prompt_len = 0;
+ if (ctl->enter_std) {
+ putp(ctl->enter_std);
+ ctl->prompt_len += (2 * ctl->stdout_glitch);
+ }
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ ctl->prompt_len += printf(_("--More--"));
+ if (filename != NULL) {
+ ctl->prompt_len += printf(_("(Next file: %s)"), filename);
+ } else if (!ctl->no_tty_in) {
+ ctl->prompt_len +=
+ printf("(%d%%)",
+ (int)((ctl->file_position * 100) / ctl->file_size));
+ }
+ if (ctl->suppress_bell) {
+ ctl->prompt_len +=
+ printf(_("[Press space to continue, 'q' to quit.]"));
+ }
+ if (ctl->exit_std)
+ putp(ctl->exit_std);
+ if (ctl->clear_line_ends)
+ putp(ctl->clear_rest);
+ } else
+ fprintf(stderr, "\a");
+ fflush(NULL);
+}
+
+static void reset_tty(struct more_control *ctl)
+{
+ if (ctl->no_tty_out)
+ return;
+ fflush(NULL);
+ ctl->output_tty.c_lflag |= ICANON | ECHO;
+ ctl->output_tty.c_cc[VMIN] = ctl->original_tty.c_cc[VMIN];
+ ctl->output_tty.c_cc[VTIME] = ctl->original_tty.c_cc[VTIME];
+ tcsetattr(STDERR_FILENO, TCSANOW, &ctl->original_tty);
+}
+
+/* Clean up terminal state and exit. Also come here if interrupt signal received */
+static void __attribute__((__noreturn__)) more_exit(struct more_control *ctl)
+{
+#ifdef HAVE_MAGIC
+ magic_close(ctl->magic);
+#endif
+ reset_tty(ctl);
+ if (ctl->clear_line_ends) {
+ putchar('\r');
+ putp(ctl->erase_line);
+ } else if (!ctl->clear_line_ends && (ctl->prompt_len > 0))
+ erase_to_col(ctl, 0);
+ fflush(NULL);
+ free(ctl->previous_search);
+ free(ctl->shell_line);
+ free(ctl->line_buf);
+ free(ctl->go_home);
+ if (ctl->current_file)
+ fclose(ctl->current_file);
+ del_curterm(cur_term);
+ _exit(EXIT_SUCCESS);
+}
+
+static cc_t read_user_input(struct more_control *ctl)
+{
+ cc_t c;
+
+ errno = 0;
+ /*
+ * Key commands can be read() from either stderr or stdin. If they
+ * are read from stdin such as 'cat file.txt | more' then the pipe
+ * input is understood as series key commands - and that is not
+ * wanted. Keep the read() reading from stderr.
+ */
+ if (read(STDERR_FILENO, &c, 1) <= 0) {
+ if (errno != EINTR)
+ more_exit(ctl);
+ else
+ c = ctl->output_tty.c_cc[VKILL];
+ }
+ return c;
+}
+
+/* Read a number and command from the terminal. Set cmd to the non-digit
+ * which terminates the number. */
+static struct number_command read_command(struct more_control *ctl)
+{
+ cc_t input[8] = { 0 };
+ ssize_t i, ilen;
+ struct number_command cmd = { .key = more_kc_unknown_command };
+
+ /* See stderr note in read_user_input() */
+ if ((ilen = read(STDERR_FILENO, &input, sizeof(input))) <= 0)
+ return cmd;
+ if (2 < ilen) {
+ if (!memcmp(input, ARROW_UP, sizeof(ARROW_UP))) {
+ cmd.key = more_kc_backwards;
+ return cmd;
+ } else if (!memcmp(input, ARROW_DOWN, sizeof(ARROW_DOWN))) {
+ cmd.key = more_kc_skip_forward;
+ return cmd;
+ } else if (!memcmp(input, PAGE_UP, sizeof(PAGE_UP))) {
+ cmd.key = more_kc_backwards;
+ return cmd;
+ } else if (!memcmp(input, PAGE_DOWN, sizeof(PAGE_DOWN))) {
+ cmd.key = more_kc_skip_forward;
+ return cmd;
+ }
+ }
+ for (i = 0; i < ilen; i++) {
+ if (isdigit(input[i])) {
+ if (0 < ctl->reading_num) {
+ ctl->leading_number *= 10;
+ ctl->leading_number += input[i] - '0';
+ } else
+ ctl->leading_number = input[i] - '0';
+ ctl->reading_num = 1;
+ continue;
+ }
+ cmd.number = ctl->leading_number;
+ ctl->reading_num = 0;
+ ctl->leading_number = 0;
+ if (ctl->leading_colon) {
+ ctl->leading_colon = 0;
+ switch (input[i]) {
+ case 'f':
+ cmd.key = more_kc_display_file_and_line;
+ return cmd;
+ case 'n':
+ cmd.key = more_kc_next_file;
+ return cmd;
+ case 'p':
+ cmd.key = more_kc_previous_file;
+ return cmd;
+ default:
+ cmd.key = more_kc_unknown_command;
+ return cmd;
+ }
+ }
+ /* command is a single char */
+ switch (input[i]) {
+ case '.':
+ cmd.key = more_kc_repeat_previous;
+ break;
+ case ':':
+ ctl->leading_colon = 1;
+ break;
+ case 'b':
+ case CTRL('B'):
+ cmd.key = more_kc_backwards;
+ break;
+ case ' ':
+ cmd.key = more_kc_jump_lines_per_screen;
+ break;
+ case 'z':
+ cmd.key = more_kc_set_lines_per_screen;
+ break;
+ case 'd':
+ case CTRL('D'):
+ cmd.key = more_kc_set_scroll_len;
+ break;
+ case 'q':
+ case 'Q':
+ cmd.key = more_kc_quit;
+ break;
+ case 'f':
+ case 's':
+ case CTRL('F'):
+ cmd.key = more_kc_skip_forward;
+ break;
+ case '\n':
+ cmd.key = more_kc_next_line;
+ break;
+ case '\f':
+ cmd.key = more_kc_clear_screen;
+ break;
+ case '\'':
+ cmd.key = more_kc_previous_search_match;
+ break;
+ case '=':
+ cmd.key = more_kc_display_line;
+ break;
+ case 'n':
+ cmd.key = more_kc_repeat_search;
+ break;
+ case '/':
+ cmd.key = more_kc_search;
+ break;
+ case '!':
+ cmd.key = more_kc_run_shell;
+ break;
+ case '?':
+ case 'h':
+ cmd.key = more_kc_help;
+ break;
+ case 'v':
+ cmd.key = more_kc_run_editor;
+ break;
+ }
+ }
+ return cmd;
+}
+
+/* Change displayed file from command line list to next nskip, where nskip
+ * is relative position in argv and can be negative, that is a previous
+ * file. */
+static void change_file(struct more_control *ctl, int nskip)
+{
+ if (nskip == 0)
+ return;
+ if (nskip > 0) {
+ if (ctl->argv_position + nskip > ctl->num_files - 1)
+ nskip = ctl->num_files - ctl->argv_position - 1;
+ }
+ ctl->argv_position += nskip;
+ if (ctl->argv_position < 0)
+ ctl->argv_position = 0;
+ puts(_("\n...Skipping "));
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ if (nskip > 0)
+ fputs(_("...Skipping to file "), stdout);
+ else
+ fputs(_("...Skipping back to file "), stdout);
+ puts(ctl->file_names[ctl->argv_position]);
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ putchar('\n');
+ ctl->argv_position--;
+}
+
+static void show(struct more_control *ctl, char c)
+{
+ if ((c < ' ' && c != '\n' && c != ESC) || c == CERASE) {
+ c += (c == CERASE) ? -0100 : 0100;
+ fputs(CARAT, stderr);
+ ctl->prompt_len++;
+ }
+ fputc(c, stderr);
+ ctl->prompt_len++;
+}
+
+static void more_error(struct more_control *ctl, char *mess)
+{
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ else
+ erase_to_col(ctl, 0);
+ ctl->prompt_len += strlen(mess);
+ if (ctl->enter_std)
+ putp(ctl->enter_std);
+ fputs(mess, stdout);
+ if (ctl->exit_std)
+ putp(ctl->exit_std);
+ fflush(NULL);
+ ctl->report_errors++;
+}
+
+static void erase_one_column(struct more_control *ctl)
+{
+ if (ctl->erase_previous_ok)
+ fprintf(stderr, "%s ", ctl->backspace_ch);
+ fputs(ctl->backspace_ch, stderr);
+}
+
+static void ttyin(struct more_control *ctl, char buf[], int nmax, char pchar)
+{
+ char *sp;
+ cc_t c;
+ int slash = 0;
+ int maxlen;
+
+ sp = buf;
+ maxlen = 0;
+ while (sp - buf < nmax) {
+ if (ctl->prompt_len > maxlen)
+ maxlen = ctl->prompt_len;
+ c = read_user_input(ctl);
+ if (c == '\\') {
+ slash++;
+ } else if (c == ctl->output_tty.c_cc[VERASE] && !slash) {
+ if (sp > buf) {
+#ifdef HAVE_WIDECHAR
+ if (MB_CUR_MAX > 1) {
+ wchar_t wc;
+ size_t pos = 0, mblength;
+ mbstate_t state, state_bak;
+
+ memset(&state, '\0', sizeof(mbstate_t));
+
+ while (1) {
+ state_bak = state;
+ mblength =
+ mbrtowc(&wc, buf + pos,
+ sp - buf, &state);
+
+ switch (mblength) {
+ case (size_t)-2:
+ case (size_t)-1:
+ state = state_bak;
+ /* fallthrough */
+ case 0:
+ mblength = 1;
+ }
+ if (buf + pos + mblength >= sp)
+ break;
+
+ pos += mblength;
+ }
+
+ if (mblength == 1) {
+ erase_one_column(ctl);
+ } else {
+ int wc_width;
+ wc_width = wcwidth(wc);
+ wc_width =
+ (wc_width <
+ 1) ? 1 : wc_width;
+ while (wc_width--) {
+ erase_one_column(ctl);
+ }
+ }
+
+ while (mblength--) {
+ --ctl->prompt_len;
+ --sp;
+ }
+ } else
+#endif /* HAVE_WIDECHAR */
+ {
+ --ctl->prompt_len;
+ erase_one_column(ctl);
+ --sp;
+ }
+
+ if ((*sp < ' ' && *sp != '\n') || *sp == CERASE) {
+ --ctl->prompt_len;
+ erase_one_column(ctl);
+ }
+ continue;
+ }
+ if (!ctl->erase_line)
+ ctl->prompt_len = maxlen;
+ } else if (c == ctl->output_tty.c_cc[VKILL] && !slash) {
+ if (ctl->hard_tty) {
+ show(ctl, c);
+ putchar('\n');
+ putchar(pchar);
+ } else {
+ putchar('\r');
+ putchar(pchar);
+ if (ctl->erase_line)
+ erase_to_col(ctl, 1);
+ else if (ctl->erase_input_ok)
+ while (ctl->prompt_len-- > 1)
+ fprintf(stderr, "%s %s", ctl->backspace_ch, ctl->backspace_ch);
+ ctl->prompt_len = 1;
+ }
+ sp = buf;
+ fflush(NULL);
+ continue;
+ }
+ if (slash && (c == ctl->output_tty.c_cc[VKILL] ||
+ c == ctl->output_tty.c_cc[VERASE])) {
+ erase_one_column(ctl);
+ --sp;
+ }
+ if (c != '\\')
+ slash = 0;
+ *sp++ = c;
+ if ((c < ' ' && c != '\n' && c != ESC) || c == CERASE) {
+ c += (c == CERASE) ? -0100 : 0100;
+ fputs(CARAT, stderr);
+ ctl->prompt_len++;
+ }
+ if (c != '\n' && c != ESC) {
+ fputc(c, stderr);
+ ctl->prompt_len++;
+ } else
+ break;
+ }
+ *--sp = '\0';
+ if (!ctl->erase_line)
+ ctl->prompt_len = maxlen;
+ if (sp - buf >= nmax - 1)
+ more_error(ctl, _("Line too long"));
+}
+
+/* Expand shell command line. */
+static void expand(struct more_control *ctl, char *inbuf)
+{
+ char *inpstr;
+ char *outstr;
+ char c;
+ char *temp;
+ int tempsz, xtra, offset;
+
+ xtra = strlen(ctl->file_names[ctl->argv_position]) + strlen(ctl->shell_line) + 1;
+ tempsz = COMMAND_BUF + xtra;
+ temp = xmalloc(tempsz);
+ inpstr = inbuf;
+ outstr = temp;
+ while ((c = *inpstr++) != '\0') {
+ offset = outstr - temp;
+ if (tempsz - offset - 1 < xtra) {
+ tempsz += COMMAND_BUF + xtra;
+ temp = xrealloc(temp, tempsz);
+ outstr = temp + offset;
+ }
+ switch (c) {
+ case '%':
+ if (!ctl->no_tty_in) {
+ strcpy(outstr, ctl->file_names[ctl->argv_position]);
+ outstr += strlen(ctl->file_names[ctl->argv_position]);
+ } else
+ *outstr++ = c;
+ break;
+ case '!':
+ if (ctl->shell_line) {
+ strcpy(outstr, ctl->shell_line);
+ outstr += strlen(ctl->shell_line);
+ } else
+ more_error(ctl, _
+ ("No previous command to substitute for"));
+ break;
+ case '\\':
+ if (*inpstr == '%' || *inpstr == '!') {
+ *outstr++ = *inpstr++;
+ break;
+ }
+ /* fallthrough */
+ default:
+ *outstr++ = c;
+ }
+ }
+ *outstr++ = '\0';
+ free(ctl->shell_line);
+ ctl->shell_line = temp;
+}
+
+static void set_tty(struct more_control *ctl)
+{
+ ctl->output_tty.c_lflag &= ~(ICANON | ECHO);
+ ctl->output_tty.c_cc[VMIN] = 1; /* read at least 1 char */
+ ctl->output_tty.c_cc[VTIME] = 0; /* no timeout */
+ tcsetattr(STDERR_FILENO, TCSANOW, &ctl->output_tty);
+}
+
+/* Come here if a quit signal is received */
+static void sigquit_handler(struct more_control *ctl)
+{
+ if (!ctl->dumb_tty && ctl->no_quit_dialog) {
+ ctl->prompt_len += fprintf(stderr, _("[Use q or Q to quit]"));
+ ctl->no_quit_dialog = 0;
+ } else
+ more_exit(ctl);
+}
+
+/* Come here when we get a suspend signal from the terminal */
+static void sigtstp_handler(struct more_control *ctl)
+{
+ reset_tty(ctl);
+ fflush(NULL);
+ kill(getpid(), SIGSTOP);
+}
+
+/* Come here when we get a continue signal from the terminal */
+static void sigcont_handler(struct more_control *ctl)
+{
+ set_tty(ctl);
+}
+
+/* Come here if a signal for a window size change is received */
+static void sigwinch_handler(struct more_control *ctl)
+{
+ struct winsize win;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1) {
+ if (win.ws_row != 0) {
+ ctl->lines_per_page = win.ws_row;
+ ctl->d_scroll_len = ctl->lines_per_page / 2 - 1;
+ if (ctl->d_scroll_len < 1)
+ ctl->d_scroll_len = 1;
+ ctl->lines_per_screen = ctl->lines_per_page - 1;
+ }
+ if (win.ws_col != 0)
+ ctl->num_columns = win.ws_col;
+ }
+ prepare_line_buffer(ctl);
+}
+
+static void execute(struct more_control *ctl, char *filename, char *cmd, ...)
+{
+ pid_t id;
+ va_list argp;
+ char *arg;
+ char **args;
+ int argcount;
+
+ fflush(NULL);
+ id = fork();
+ if (id == 0) {
+ int errsv;
+ if (!isatty(STDIN_FILENO)) {
+ close(STDIN_FILENO);
+ open("/dev/tty", 0);
+ }
+ reset_tty(ctl);
+
+ va_start(argp, cmd);
+ arg = va_arg(argp, char *);
+ argcount = 0;
+ while (arg) {
+ argcount++;
+ arg = va_arg(argp, char *);
+ }
+ va_end(argp);
+
+ args = alloca(sizeof(char *) * (argcount + 1));
+ args[argcount] = NULL;
+
+ va_start(argp, cmd);
+ arg = va_arg(argp, char *);
+ argcount = 0;
+ while (arg) {
+ args[argcount] = arg;
+ argcount++;
+ arg = va_arg(argp, char *);
+ }
+ va_end(argp);
+
+ if (geteuid() != getuid() || getegid() != getgid()) {
+ if (setuid(getuid()) < 0)
+ err(EXIT_FAILURE, _("setuid failed"));
+ if (setgid(getgid()) < 0)
+ err(EXIT_FAILURE, _("setgid failed"));
+ }
+
+ execvp(cmd, args);
+ errsv = errno;
+ fputs(_("exec failed\n"), stderr);
+ exit(errsv == ENOENT ? EX_EXEC_ENOENT : EX_EXEC_FAILED);
+ }
+ if (id > 0) {
+ errno = 0;
+ while (wait(NULL) > 0) {
+ if (errno == EINTR)
+ continue;
+ }
+ } else
+ fputs(_("can't fork\n"), stderr);
+ set_tty(ctl);
+ print_separator('-', 24);
+ output_prompt(ctl, filename);
+}
+
+static void run_shell(struct more_control *ctl, char *filename)
+{
+ char cmdbuf[COMMAND_BUF];
+
+ erase_to_col(ctl, 0);
+ putchar('!');
+ fflush(NULL);
+ if (ctl->previous_command.key == more_kc_run_shell && ctl->shell_line)
+ fputs(ctl->shell_line, stdout);
+ else {
+ ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '!');
+ if (strpbrk(cmdbuf, "%!\\"))
+ expand(ctl, cmdbuf);
+ else {
+ free(ctl->shell_line);
+ ctl->shell_line = xstrdup(cmdbuf);
+ }
+ }
+ fputc('\n', stderr);
+ fflush(NULL);
+ ctl->prompt_len = 0;
+ execute(ctl, filename, ctl->shell, ctl->shell, "-c", ctl->shell_line, 0);
+}
+
+/* Skip n lines in the file f */
+static void skip_lines(struct more_control *ctl)
+{
+ int c;
+
+ while (ctl->next_jump > 0) {
+ while ((c = more_getc(ctl)) != '\n')
+ if (c == EOF)
+ return;
+ ctl->next_jump--;
+ ctl->current_line++;
+ }
+}
+
+/* Clear the screen */
+static void more_clear_screen(struct more_control *ctl)
+{
+ if (ctl->clear && !ctl->hard_tty) {
+ putp(ctl->clear);
+ /* Put out carriage return so that system doesn't get
+ * confused by escape sequences when expanding tabs */
+ putchar('\r');
+ ctl->prompt_len = 0;
+ }
+}
+
+static void read_line(struct more_control *ctl)
+{
+ int c;
+ char *p;
+
+ p = ctl->line_buf;
+ while ((c = more_getc(ctl)) != '\n' && c != EOF
+ && (ptrdiff_t)p != (ptrdiff_t)(ctl->line_buf + ctl->line_sz - 1))
+ *p++ = c;
+ if (c == '\n')
+ ctl->current_line++;
+ *p = '\0';
+}
+
+static int more_poll(struct more_control *ctl, int timeout)
+{
+ struct pollfd pfd[2];
+
+ pfd[0].fd = ctl->sigfd;
+ pfd[0].events = POLLIN | POLLERR | POLLHUP;
+ pfd[1].fd = STDIN_FILENO;
+ pfd[1].events = POLLIN;
+
+ if (poll(pfd, 2, timeout) < 0) {
+ if (errno == EAGAIN)
+ return 1;
+ more_error(ctl, _("poll failed"));
+ return 1;
+ }
+ if (pfd[0].revents != 0) {
+ struct signalfd_siginfo info;
+ ssize_t sz;
+
+ sz = read(pfd[0].fd, &info, sizeof(info));
+ assert(sz == sizeof(info));
+ switch (info.ssi_signo) {
+ case SIGINT:
+ more_exit(ctl);
+ break;
+ case SIGQUIT:
+ sigquit_handler(ctl);
+ break;
+ case SIGTSTP:
+ sigtstp_handler(ctl);
+ break;
+ case SIGCONT:
+ sigcont_handler(ctl);
+ break;
+ case SIGWINCH:
+ sigwinch_handler(ctl);
+ break;
+ default:
+ abort();
+ }
+ }
+ if (pfd[1].revents == 0)
+ return 1;
+ return 0;
+}
+
+/* Search for nth occurrence of regular expression contained in buf in
+ * the file */
+static void search(struct more_control *ctl, char buf[], int n)
+{
+ off_t startline = ctl->file_position;
+ off_t line1 = startline;
+ off_t line2 = startline;
+ off_t line3;
+ int lncount;
+ int saveln, rc;
+ regex_t re;
+
+ if (buf != ctl->previous_search) {
+ free(ctl->previous_search);
+ ctl->previous_search = buf;
+ }
+
+ ctl->search_called = 1;
+ ctl->context.line_num = saveln = ctl->current_line;
+ ctl->context.row_num = startline;
+ lncount = 0;
+ if (!buf)
+ goto notfound;
+ if ((rc = regcomp(&re, buf, REG_NOSUB)) != 0) {
+ char s[REGERR_BUF];
+ regerror(rc, &re, s, sizeof s);
+ more_error(ctl, s);
+ return;
+ }
+ while (!feof(ctl->current_file)) {
+ line3 = line2;
+ line2 = line1;
+ line1 = ctl->file_position;
+ read_line(ctl);
+ lncount++;
+ if (regexec(&re, ctl->line_buf, 0, NULL, 0) == 0 && --n == 0) {
+ if ((1 < lncount && ctl->no_tty_in) || 3 < lncount) {
+ putchar('\n');
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ fputs(_("...skipping\n"), stdout);
+ }
+ if (!ctl->no_tty_in) {
+ ctl->current_line -= (lncount < 3 ? lncount : 3);
+ more_fseek(ctl, line3);
+ if (ctl->no_scroll) {
+ if (ctl->clear_line_ends) {
+ putp(ctl->go_home);
+ putp(ctl->erase_line);
+ } else
+ more_clear_screen(ctl);
+ }
+ } else {
+ erase_to_col(ctl, 0);
+ if (ctl->no_scroll) {
+ if (ctl->clear_line_ends) {
+ putp(ctl->go_home);
+ putp(ctl->erase_line);
+ } else
+ more_clear_screen(ctl);
+ }
+ puts(ctl->line_buf);
+ }
+ break;
+ }
+ more_poll(ctl, 1);
+ }
+ /* Move ctrl+c signal handling back to more_key_command(). */
+ signal(SIGINT, SIG_DFL);
+ sigaddset(&ctl->sigset, SIGINT);
+ sigprocmask(SIG_BLOCK, &ctl->sigset, NULL);
+ regfree(&re);
+ if (feof(ctl->current_file)) {
+ if (!ctl->no_tty_in) {
+ ctl->current_line = saveln;
+ more_fseek(ctl, startline);
+ } else {
+ fputs(_("\nPattern not found\n"), stdout);
+ more_exit(ctl);
+ }
+notfound:
+ more_error(ctl, _("Pattern not found"));
+ }
+}
+
+static char *find_editor(void)
+{
+ static char *editor;
+
+ editor = getenv("VISUAL");
+ if (editor == NULL || *editor == '\0')
+ editor = getenv("EDITOR");
+ if (editor == NULL || *editor == '\0')
+ editor = _PATH_VI;
+ return editor;
+}
+
+static void runtime_usage(void)
+{
+ fputs(_("Most commands optionally preceded by integer argument k. "
+ "Defaults in brackets.\n"
+ "Star (*) indicates argument becomes new default.\n"), stdout);
+ print_separator('-', 79);
+ fprintf(stdout,
+ _
+ ("<space> Display next k lines of text [current screen size]\n"
+ "z Display next k lines of text [current screen size]*\n"
+ "<return> Display next k lines of text [1]*\n"
+ "d or ctrl-D Scroll k lines [current scroll size, initially 11]*\n"
+ "q or Q or <interrupt> Exit from more\n"
+ "s Skip forward k lines of text [1]\n"
+ "f Skip forward k screenfuls of text [1]\n"
+ "b or ctrl-B Skip backwards k screenfuls of text [1]\n"
+ "' Go to place where previous search started\n"
+ "= Display current line number\n"
+ "/<regular expression> Search for kth occurrence of regular expression [1]\n"
+ "n Search for kth occurrence of last r.e [1]\n"
+ "!<cmd> or :!<cmd> Execute <cmd> in a subshell\n"
+ "v Start up '%s' at current line\n"
+ "ctrl-L Redraw screen\n"
+ ":n Go to kth next file [1]\n"
+ ":p Go to kth previous file [1]\n"
+ ":f Display current file name and line number\n"
+ ". Repeat previous command\n"),
+ find_editor());
+ print_separator('-', 79);
+}
+
+static void execute_editor(struct more_control *ctl, char *cmdbuf, char *filename)
+{
+ char *editor, *p;
+ int split = 0;
+ int n;
+
+ if ((ctl->current_line - ctl->lines_per_screen) < 1)
+ n = 1;
+ else
+ n = ctl->current_line - (ctl->lines_per_screen + 1) / 2;
+ editor = find_editor();
+ p = strrchr(editor, '/');
+ if (p)
+ p++;
+ else
+ p = editor;
+ /*
+ * Earlier: call vi +n file. This also works for emacs.
+ * POSIX: call vi -c n file (when editor is vi or ex).
+ */
+ if (!strcmp(p, "vi") || !strcmp(p, "ex")) {
+ sprintf(cmdbuf, "-c %d", n);
+ split = 1;
+ } else
+ sprintf(cmdbuf, "+%d", n);
+
+ erase_to_col(ctl, 0);
+ printf("%s %s %s", editor, cmdbuf, ctl->file_names[ctl->argv_position]);
+ if (split) {
+ cmdbuf[2] = 0;
+ execute(ctl, filename, editor, editor,
+ cmdbuf, cmdbuf + 3,
+ ctl->file_names[ctl->argv_position], (char *)0);
+ } else
+ execute(ctl, filename, editor, editor,
+ cmdbuf, ctl->file_names[ctl->argv_position], (char *)0);
+}
+
+static int skip_backwards(struct more_control *ctl, int nlines)
+{
+ if (nlines == 0)
+ nlines++;
+ erase_to_col(ctl, 0);
+ printf(P_("...back %d page", "...back %d pages", nlines), nlines);
+ putchar('\n');
+ ctl->next_jump = ctl->current_line - (ctl->lines_per_screen * (nlines + 1)) - 1;
+ if (ctl->next_jump < 0)
+ ctl->next_jump = 0;
+ more_fseek(ctl, 0);
+ ctl->current_line = 0;
+ skip_lines(ctl);
+ return ctl->lines_per_screen;
+}
+
+static int skip_forwards(struct more_control *ctl, int nlines, cc_t comchar)
+{
+ int c;
+
+ if (nlines == 0)
+ nlines++;
+ if (comchar == 'f')
+ nlines *= ctl->lines_per_screen;
+ putchar('\r');
+ erase_to_col(ctl, 0);
+ putchar('\n');
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ printf(P_("...skipping %d line",
+ "...skipping %d lines", nlines), nlines);
+
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ putchar('\n');
+
+ while (nlines > 0) {
+ while ((c = more_getc(ctl)) != '\n')
+ if (c == EOF)
+ return 0;
+ ctl->current_line++;
+ nlines--;
+ }
+ return 1;
+}
+
+/* Read a command and do it. A command consists of an optional integer
+ * argument followed by the command character. Return the number of
+ * lines to display in the next screenful. If there is nothing more to
+ * display in the current file, zero is returned. */
+static int more_key_command(struct more_control *ctl, char *filename)
+{
+ int retval = 0;
+ int done = 0, search_again = 0;
+ char cmdbuf[INIT_BUF];
+ struct number_command cmd;
+
+ if (!ctl->report_errors)
+ output_prompt(ctl, filename);
+ else
+ ctl->report_errors = 0;
+ ctl->search_called = 0;
+ for (;;) {
+ if (more_poll(ctl, -1) != 0)
+ continue;
+ cmd = read_command(ctl);
+ if (cmd.key == more_kc_unknown_command)
+ continue;
+ if (cmd.key == more_kc_repeat_previous)
+ cmd = ctl->previous_command;
+ switch (cmd.key) {
+ case more_kc_backwards:
+ if (ctl->no_tty_in) {
+ fprintf(stderr, "\a");
+ return -1;
+ }
+ retval = skip_backwards(ctl, cmd.number);
+ done = 1;
+ break;
+ case more_kc_jump_lines_per_screen:
+ case more_kc_set_lines_per_screen:
+ if (cmd.number == 0)
+ cmd.number = ctl->lines_per_screen;
+ else if (cmd.key == more_kc_set_lines_per_screen)
+ ctl->lines_per_screen = cmd.number;
+ retval = cmd.number;
+ done = 1;
+ break;
+ case more_kc_set_scroll_len:
+ if (cmd.number != 0)
+ ctl->d_scroll_len = cmd.number;
+ retval = ctl->d_scroll_len;
+ done = 1;
+ break;
+ case more_kc_quit:
+ more_exit(ctl);
+ case more_kc_skip_forward:
+ if (skip_forwards(ctl, cmd.number, cmd.number))
+ retval = ctl->lines_per_screen;
+ done = 1;
+ break;
+ case more_kc_next_line:
+ if (cmd.number != 0)
+ ctl->lines_per_screen = cmd.number;
+ else
+ cmd.number = 1;
+ retval = cmd.number;
+ done = 1;
+ break;
+ case more_kc_clear_screen:
+ if (!ctl->no_tty_in) {
+ more_clear_screen(ctl);
+ more_fseek(ctl, ctl->screen_start.row_num);
+ ctl->current_line = ctl->screen_start.line_num;
+ retval = ctl->lines_per_screen;
+ done = 1;
+ break;
+ } else {
+ fprintf(stderr, "\a");
+ break;
+ }
+ case more_kc_previous_search_match:
+ if (!ctl->no_tty_in) {
+ erase_to_col(ctl, 0);
+ fputs(_("\n***Back***\n\n"), stdout);
+ more_fseek(ctl, ctl->context.row_num);
+ ctl->current_line = ctl->context.line_num;
+ retval = ctl->lines_per_screen;
+ done = 1;
+ break;
+ } else {
+ fprintf(stderr, "\a");
+ break;
+ }
+ case more_kc_display_line:
+ erase_to_col(ctl, 0);
+ ctl->prompt_len = printf("%d", ctl->current_line);
+ fflush(NULL);
+ break;
+ case more_kc_display_file_and_line:
+ erase_to_col(ctl, 0);
+ if (!ctl->no_tty_in)
+ ctl->prompt_len =
+ printf(_("\"%s\" line %d"),
+ ctl->file_names[ctl->argv_position], ctl->current_line);
+ else
+ ctl->prompt_len = printf(_("[Not a file] line %d"),
+ ctl->current_line);
+ fflush(NULL);
+ break;
+ case more_kc_repeat_search:
+ if (!ctl->previous_search) {
+ more_error(ctl, _("No previous regular expression"));
+ break;
+ }
+ search_again = 1;
+ /* fallthrough */
+ case more_kc_search:
+ if (cmd.number == 0)
+ cmd.number++;
+ erase_to_col(ctl, 0);
+ putchar('/');
+ ctl->prompt_len = 1;
+ fflush(NULL);
+ if (search_again) {
+ fputc('\r', stderr);
+ search(ctl, ctl->previous_search, cmd.number);
+ search_again = 0;
+ } else {
+ ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '/');
+ fputc('\r', stderr);
+ ctl->next_search = xstrdup(cmdbuf);
+ search(ctl, ctl->next_search, cmd.number);
+ }
+ retval = ctl->lines_per_screen - 1;
+ done = 1;
+ break;
+ case more_kc_run_shell:
+ run_shell(ctl, filename);
+ break;
+ case more_kc_help:
+ if (ctl->no_scroll)
+ more_clear_screen(ctl);
+ erase_to_col(ctl, 0);
+ runtime_usage();
+ output_prompt(ctl, filename);
+ break;
+ case more_kc_next_file:
+ putchar('\r');
+ erase_to_col(ctl, 0);
+ if (cmd.number == 0)
+ cmd.number = 1;
+ if (ctl->argv_position + cmd.number >= (unsigned int)ctl->num_files)
+ more_exit(ctl);
+ change_file(ctl, cmd.number);
+ done = 1;
+ break;
+ case more_kc_previous_file:
+ if (ctl->no_tty_in) {
+ fprintf(stderr, "\a");
+ break;
+ }
+ putchar('\r');
+ erase_to_col(ctl, 0);
+ if (cmd.number == 0)
+ cmd.number = 1;
+ change_file(ctl, -cmd.number);
+ done = 1;
+ break;
+ case more_kc_run_editor: /* This case should go right before default */
+ if (!ctl->no_tty_in) {
+ execute_editor(ctl, cmdbuf, filename);
+ break;
+ }
+ /* fallthrough */
+ default:
+ if (ctl->suppress_bell) {
+ erase_to_col(ctl, 0);
+ if (ctl->enter_std)
+ putp(ctl->enter_std);
+ ctl->prompt_len =
+ printf(_("[Press 'h' for instructions.]"))
+ + 2 * ctl->stdout_glitch;
+ if (ctl->exit_std)
+ putp(ctl->exit_std);
+ } else
+ fprintf(stderr, "\a");
+ fflush(NULL);
+ break;
+ }
+ ctl->previous_command = cmd;
+ if (done) {
+ cmd.key = more_kc_unknown_command;
+ break;
+ }
+ }
+ putchar('\r');
+ ctl->no_quit_dialog = 1;
+ return retval;
+}
+
+/* Print out the contents of the file f, one screenful at a time. */
+static void screen(struct more_control *ctl, int num_lines)
+{
+ int c;
+ int nchars;
+ int length; /* length of current line */
+ static int prev_len = 1; /* length of previous line */
+
+ for (;;) {
+ while (num_lines > 0 && !ctl->is_paused) {
+ if ((nchars = get_line(ctl, &length)) == EOF) {
+ if (ctl->clear_line_ends)
+ putp(ctl->clear_rest);
+ return;
+ }
+ if (ctl->squeeze_spaces && length == 0 && prev_len == 0)
+ continue;
+ prev_len = length;
+ if (ctl->bad_stdout
+ || ((ctl->enter_std && *ctl->enter_std == ' ') && (ctl->prompt_len > 0)))
+ erase_to_col(ctl, 0);
+ /* must clear before drawing line since tabs on
+ * some terminals do not erase what they tab
+ * over. */
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ fwrite(ctl->line_buf, length, 1, stdout);
+ if (nchars < ctl->prompt_len)
+ erase_to_col(ctl, nchars);
+ ctl->prompt_len = 0;
+ if (nchars < ctl->num_columns || !ctl->fold_long_lines)
+ putchar('\n');
+ num_lines--;
+ }
+ fflush(NULL);
+ if ((c = more_getc(ctl)) == EOF) {
+ if (ctl->clear_line_ends)
+ putp(ctl->clear_rest);
+ return;
+ }
+
+ if (ctl->is_paused && ctl->clear_line_ends)
+ putp(ctl->clear_rest);
+ more_ungetc(ctl, c);
+ ctl->is_paused = 0;
+ do {
+ if ((num_lines = more_key_command(ctl, NULL)) == 0)
+ return;
+ } while (ctl->search_called && !ctl->previous_search);
+ if (ctl->hard_tty && ctl->prompt_len > 0)
+ erase_to_col(ctl, 0);
+ if (ctl->no_scroll && num_lines >= ctl->lines_per_screen) {
+ if (ctl->clear_line_ends)
+ putp(ctl->go_home);
+ else
+ more_clear_screen(ctl);
+ }
+ ctl->screen_start.line_num = ctl->current_line;
+ ctl->screen_start.row_num = ctl->file_position;
+ }
+}
+
+static void copy_file(FILE *f)
+{
+ char buf[BUFSIZ];
+ size_t sz;
+
+ while ((sz = fread(&buf, sizeof(char), sizeof(buf), f)) > 0)
+ fwrite(&buf, sizeof(char), sz, stdout);
+}
+
+
+static void display_file(struct more_control *ctl, int left)
+{
+ if (!ctl->current_file)
+ return;
+ ctl->context.line_num = ctl->context.row_num = 0;
+ ctl->current_line = 0;
+ if (ctl->first_file) {
+ ctl->first_file = 0;
+ if (ctl->next_jump)
+ skip_lines(ctl);
+ if (ctl->search_at_start) {
+ search(ctl, ctl->next_search, 1);
+ if (ctl->no_scroll)
+ left--;
+ }
+ } else if (ctl->argv_position < ctl->num_files && !ctl->no_tty_out)
+ left =
+ more_key_command(ctl, ctl->file_names[ctl->argv_position]);
+ if (left != 0) {
+ if ((ctl->no_scroll || ctl->clear_first)
+ && ctl->file_size != ~((off_t)0)) {
+ if (ctl->clear_line_ends)
+ putp(ctl->go_home);
+ else
+ more_clear_screen(ctl);
+ }
+ if (ctl->print_banner) {
+ if (ctl->bad_stdout)
+ erase_to_col(ctl, 0);
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ if (ctl->prompt_len > 14)
+ erase_to_col(ctl, 14);
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ print_separator(':', 14);
+ puts(ctl->file_names[ctl->argv_position]);
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ print_separator(':', 14);
+ if (left > ctl->lines_per_page - 4)
+ left = ctl->lines_per_page - 4;
+ }
+ if (ctl->no_tty_out)
+ copy_file(ctl->current_file);
+ else
+ screen(ctl, left);
+ }
+ fflush(NULL);
+ fclose(ctl->current_file);
+ ctl->current_file = NULL;
+ ctl->screen_start.line_num = ctl->screen_start.row_num = 0;
+ ctl->context.line_num = ctl->context.row_num = 0L;
+}
+
+static void initterm(struct more_control *ctl)
+{
+ int ret;
+ char *term;
+ struct winsize win;
+ char *cursor_addr;
+
+#ifndef NON_INTERACTIVE_MORE
+ ctl->no_tty_out = tcgetattr(STDOUT_FILENO, &ctl->output_tty);
+#endif
+ ctl->no_tty_in = tcgetattr(STDIN_FILENO, &ctl->output_tty);
+ tcgetattr(STDERR_FILENO, &ctl->output_tty);
+ ctl->original_tty = ctl->output_tty;
+ ctl->hard_tabs = (ctl->output_tty.c_oflag & TABDLY) != TAB3;
+ if (ctl->no_tty_out)
+ return;
+
+ ctl->output_tty.c_lflag &= ~(ICANON | ECHO);
+ ctl->output_tty.c_cc[VMIN] = 1;
+ ctl->output_tty.c_cc[VTIME] = 0;
+ ctl->erase_previous_ok = (ctl->output_tty.c_cc[VERASE] != 255);
+ ctl->erase_input_ok = (ctl->output_tty.c_cc[VKILL] != 255);
+ if ((term = getenv("TERM")) == NULL) {
+ ctl->dumb_tty = 1;
+ }
+ setupterm(term, 1, &ret);
+ if (ret <= 0) {
+ ctl->dumb_tty = 1;
+ return;
+ }
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0) {
+ ctl->lines_per_page = tigetnum(TERM_LINES);
+ ctl->num_columns = tigetnum(TERM_COLS);
+ } else {
+ if ((ctl->lines_per_page = win.ws_row) == 0)
+ ctl->lines_per_page = tigetnum(TERM_LINES);
+ if ((ctl->num_columns = win.ws_col) == 0)
+ ctl->num_columns = tigetnum(TERM_COLS);
+ }
+ if ((ctl->lines_per_page <= 0) || tigetflag(TERM_HARD_COPY)) {
+ ctl->hard_tty = 1;
+ ctl->lines_per_page = LINES_PER_PAGE;
+ }
+
+ if (tigetflag(TERM_EAT_NEW_LINE))
+ /* Eat newline at last column + 1; dec, concept */
+ ctl->eat_newline++;
+ if (ctl->num_columns <= 0)
+ ctl->num_columns = NUM_COLUMNS;
+
+ ctl->wrap_margin = tigetflag(TERM_AUTO_RIGHT_MARGIN);
+ ctl->bad_stdout = tigetflag(TERM_CEOL);
+ ctl->erase_line = tigetstr(TERM_CLEAR_TO_LINE_END);
+ ctl->clear = tigetstr(TERM_CLEAR);
+ if ((ctl->enter_std = tigetstr(TERM_STANDARD_MODE)) != NULL) {
+ ctl->exit_std = tigetstr(TERM_EXIT_STANDARD_MODE);
+ if (0 < tigetnum(TERM_STD_MODE_GLITCH))
+ ctl->stdout_glitch = 1;
+ }
+
+ cursor_addr = tigetstr(TERM_HOME);
+ if (cursor_addr == NULL || *cursor_addr == '\0') {
+ cursor_addr = tigetstr(TERM_CURSOR_ADDRESS);
+ if (cursor_addr)
+ cursor_addr = tparm(cursor_addr, 0, 0);
+ }
+ if (cursor_addr)
+ ctl->go_home = xstrdup(cursor_addr);
+
+ if ((ctl->move_line_down = tigetstr(TERM_LINE_DOWN)) == NULL)
+ ctl->move_line_down = BACKSPACE;
+ ctl->clear_rest = tigetstr(TERM_CLEAR_TO_SCREEN_END);
+ if ((ctl->backspace_ch = tigetstr(TERM_BACKSPACE)) == NULL)
+ ctl->backspace_ch = BACKSPACE;
+
+ if ((ctl->shell = getenv("SHELL")) == NULL)
+ ctl->shell = _PATH_BSHELL;
+}
+
+int main(int argc, char **argv)
+{
+ char *s;
+ int left;
+ struct more_control ctl = {
+ .first_file = 1,
+ .fold_long_lines = 1,
+ .no_quit_dialog = 1,
+ .stop_after_formfeed = 1,
+ .wrap_margin = 1,
+ .lines_per_page = LINES_PER_PAGE,
+ .num_columns = NUM_COLUMNS,
+ .d_scroll_len = SCROLL_LEN,
+ 0
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+ setlocale(LC_ALL, "");
+
+ /* Auto set no scroll on when binary is called page */
+ if (!(strcmp(program_invocation_short_name, "page")))
+ ctl.no_scroll++;
+
+ if ((s = getenv("MORE")) != NULL)
+ env_argscan(&ctl, s);
+ argscan(&ctl, argc, argv);
+
+ initterm(&ctl);
+
+#ifdef HAVE_MAGIC
+ ctl.magic = magic_open(MAGIC_MIME_ENCODING | MAGIC_SYMLINK);
+ magic_load(ctl.magic, NULL);
+#endif
+ prepare_line_buffer(&ctl);
+
+ ctl.d_scroll_len = ctl.lines_per_page / 2 - 1;
+ if (ctl.d_scroll_len <= 0)
+ ctl.d_scroll_len = 1;
+
+ /* allow clear_line_ends only if go_home and erase_line and clear_rest strings are
+ * defined, and in that case, make sure we are in no_scroll mode */
+ if (ctl.clear_line_ends) {
+ if ((ctl.go_home == NULL) || (*ctl.go_home == '\0') ||
+ (ctl.erase_line == NULL) || (*ctl.erase_line == '\0') ||
+ (ctl.clear_rest == NULL) || (*ctl.clear_rest == '\0'))
+ ctl.clear_line_ends = 0;
+ else
+ ctl.no_scroll = 1;
+ }
+ if (ctl.lines_per_screen == 0)
+ ctl.lines_per_screen = ctl.lines_per_page - 1;
+ left = ctl.lines_per_screen;
+ if (ctl.num_files > 1)
+ ctl.print_banner = 1;
+ if (!ctl.no_tty_in && ctl.num_files == 0) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ } else
+ ctl.current_file = stdin;
+ if (!ctl.no_tty_out) {
+ if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) {
+ ctl.catch_suspend++;
+ }
+ tcsetattr(STDERR_FILENO, TCSANOW, &ctl.output_tty);
+ }
+ sigemptyset(&ctl.sigset);
+ sigaddset(&ctl.sigset, SIGINT);
+ sigaddset(&ctl.sigset, SIGQUIT);
+ sigaddset(&ctl.sigset, SIGTSTP);
+ sigaddset(&ctl.sigset, SIGCONT);
+ sigaddset(&ctl.sigset, SIGWINCH);
+ sigprocmask(SIG_BLOCK, &ctl.sigset, NULL);
+ ctl.sigfd = signalfd(-1, &ctl.sigset, SFD_CLOEXEC);
+ if (ctl.no_tty_in) {
+ if (ctl.no_tty_out)
+ copy_file(stdin);
+ else {
+ ctl.current_file = stdin;
+ display_file(&ctl, left);
+ }
+ ctl.no_tty_in = 0;
+ ctl.print_banner = 1;
+ ctl.first_file = 0;
+ }
+
+ while (ctl.argv_position < ctl.num_files) {
+ checkf(&ctl, ctl.file_names[ctl.argv_position]);
+ display_file(&ctl, left);
+ ctl.first_file = 0;
+ ctl.argv_position++;
+ }
+ ctl.clear_line_ends = 0;
+ ctl.prompt_len = 0;
+ more_exit(&ctl);
+}
diff --git a/text-utils/pg.1 b/text-utils/pg.1
new file mode 100644
index 0000000..1200ab8
--- /dev/null
+++ b/text-utils/pg.1
@@ -0,0 +1,238 @@
+.\" Copyright 2001 Gunnar Ritter
+.TH PG 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+pg \- browse pagewise through text files
+.SH SYNOPSIS
+.B pg
+.RB [ \-\fIamount\fP ]
+.RB [ \-p
+.IR prompt ]
+.RB [ \-cefnrs ]
+.RB [ +\fIline\fP ]
+.RB [ +/\fIpattern\fP/ ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B pg
+displays a text file on a
+.SM CRT
+one screenful at once.
+After each page, a prompt is displayed. The user may then either press the
+newline key to view the next page or one of the keys described below.
+.PP
+If no filename is given on the command line,
+.B pg
+reads from standard input.
+If standard output is not a terminal,
+.B pg
+acts like
+.BR cat (1)
+but precedes each file with its name if there is more than one.
+.PP
+If input comes from a pipe,
+.B pg
+stores the data in a buffer file while reading,
+to make navigation possible.
+.SH OPTIONS
+.B pg
+accepts the following options:
+.TP
+.BI + number
+Start at the given line number.
+.TP
+.BI +/ pattern /
+Start at the line containing the Basic Regular Expression
+.I pattern
+given.
+.TP
+.BI \- number
+The number of lines per page. By default, this is the number of
+.SM CRT
+lines minus one.
+.TP
+.B \-c
+Clear the screen before a page is displayed,
+if the terminfo entry for the terminal provides this capability.
+.TP
+.B \-e
+Do not pause and display
+.SM (EOF)
+at the end of a file.
+.TP
+.B \-f
+Do not split long lines.
+.TP
+.B \-n
+Without this option, commands must be terminated by a newline character.
+With this option,
+.B pg
+advances once a command letter is entered.
+.TP
+.BI \-p \ string
+Instead of the normal prompt
+.IR : ,
+.I string
+is displayed.
+If
+.I string
+contains
+.BR %d ,
+its first occurrence is replaced by the number of the current page.
+.TP
+.B \-r
+Disallow the shell escape.
+.TP
+.B \-s
+Print messages in
+.I standout
+mode,
+if the terminfo entry for the terminal provides this capability.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH COMMANDS
+The following commands may be entered at the prompt. Commands preceded by
+.I i
+in this document accept a number as argument, positive or negative.
+If this argument starts with
+.B +
+or
+.BR \- ,
+it is interpreted relative to the current position in the input file,
+otherwise relative to the beginning.
+.TP
+.IB i <Enter>
+Display the next or the indicated page.
+.TP
+\fIi\fR\fBd\fR or \fB^D\fR
+Display the next halfpage. If
+.I i
+is given, it is always interpreted relative to the current position.
+.TP
+.IB i l
+Display the next or the indicated line.
+.TP
+.IB i f
+Skip a page forward.
+.I i
+must be a positive number and is always interpreted relative
+to the current position.
+.TP
+\fIi\fR\fBw\fR or \fIi\fR\fBz\fR
+As
+.B <Enter>
+except that
+.I i
+becomes the new page size.
+.TP
+.BR . " or " ^L
+Redraw the screen.
+.TP
+.B $
+Advance to the last line of the input file.
+.TP
+.IB i / pattern /
+Search forward until the first or the \fIi\fR-th
+occurrence of the Basic Regular Expression
+.I pattern
+is found. The search starts
+after the current page and stops at the end of the file.
+No wrap-around is performed.
+.I i
+must be a positive number.
+.TP
+\fIi\fR\fB?\fR\fIpattern\fR\fB?\fR or \fIi\fR\fB^\fR\fIpattern\fR\fB^\fR
+Search backward until the first or the \fIi\fR-th
+occurrence of the Basic Regular Expression
+.I pattern
+is found. The search starts
+before the current page and stops at the beginning of the file.
+No wrap-around is performed.
+.I i
+must be a positive number.
+.PP
+The search commands accept an added letter. If
+.B t
+is given, the line containing the pattern is displayed at the top of the
+screen, which is the default.
+.B m
+selects the middle and
+.B b
+the bottom of the screen.
+The selected position is used in following searches, too.
+.TP
+.IB i n
+Advance to the next file or
+.I i
+files forward.
+.TP
+.IB i p
+Reread the previous file or
+.I i
+files backward.
+.TP
+.BI s \ filename
+Save the current file to the given
+.I filename.
+.TP
+.B h
+Display a command summary.
+.TP
+.BI ! command
+Execute
+.I command
+using the shell.
+.TP
+.BR q " or " Q
+Quit.
+.PP
+If the user presses the interrupt or quit key while
+.B pg
+reads from the
+input file or writes on the terminal,
+.B pg
+will immediately display the prompt.
+In all other situations these keys will terminate
+.BR pg .
+.SH ENVIRONMENT
+The following environment variables
+affect the behavior of
+.BR pg :
+.TP
+.B COLUMNS
+Overrides the system-supplied number of columns if set.
+.TP
+.BR LANG ,\ LC_ALL ,\ LC_COLLATE ,\ LC_CTYPE ,\ LC_MESSAGES
+See
+.BR locale (7).
+.TP
+.B LINES
+Overrides the system-supplied number of lines if set.
+.TP
+.B SHELL
+Used by the
+.BR ! " command."
+.TP
+.B TERM
+Determines the terminal type.
+.SH NOTES
+.B pg
+expects the terminal tabulators to be set every eight positions.
+.PP
+Files that include
+.SM NUL
+characters cannot be displayed by
+.BR pg .
+.SH SEE ALSO
+.BR cat (1),
+.BR more (1),
+.BR sh (1p),
+.BR terminfo (5),
+.BR locale (7),
+.BR regex (7),
+.BR term (7)
+.SH AVAILABILITY
+The pg command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/text-utils/pg.c b/text-utils/pg.c
new file mode 100644
index 0000000..9da0070
--- /dev/null
+++ b/text-utils/pg.c
@@ -0,0 +1,1694 @@
+/*
+ * pg - A clone of the System V CRT paging utility.
+ *
+ * Copyright (c) 2000-2001 Gunnar Ritter. 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. [deleted]
+ * 4. Neither the name of Gunnar Ritter nor the names of his contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
+ */
+
+/* Sccsid @(#)pg.c 1.44 (gritter) 2/8/02 - modified for util-linux */
+
+/*
+ * This command is deprecated. The utility is in maintenance mode,
+ * meaning we keep them in source tree for backward compatibility
+ * only. Do not waste time making this command better, unless the
+ * fix is about security or other very critical issue.
+ *
+ * See Documentation/deprecated.txt for more information.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#ifndef TIOCGWINSZ
+# include <sys/ioctl.h>
+#endif
+#include <termios.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <setjmp.h>
+
+#if defined(HAVE_NCURSESW_NCURSES_H)
+# include <ncursesw/ncurses.h>
+#elif defined(HAVE_NCURSES_NCURSES_H)
+# include <ncurses/ncurses.h>
+#elif defined(HAVE_NCURSES_H)
+# include <ncurses.h>
+#endif
+
+#if defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+#elif defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+#elif defined(HAVE_TERM_H)
+# include <term.h>
+#endif
+
+#include "nls.h"
+#include "xalloc.h"
+#include "widechar.h"
+#include "all-io.h"
+#include "closestream.h"
+#include "strutils.h"
+
+#define READBUF LINE_MAX /* size of input buffer */
+#define CMDBUF 255 /* size of command buffer */
+#define PG_TABSIZE 8 /* spaces consumed by tab character */
+
+#define cuc(c) ((c) & 0377)
+
+enum { FORWARD = 1, BACKWARD = 2 }; /* search direction */
+enum { TOP, MIDDLE, BOTTOM }; /* position of matching line */
+
+/* States for syntax-aware command line editor. */
+enum {
+ COUNT,
+ SIGN,
+ CMD_FIN,
+ SEARCH,
+ SEARCH_FIN,
+ ADDON_FIN,
+ STRING,
+ INVALID
+};
+
+/* Current command */
+static struct {
+ char cmdline[CMDBUF];
+ size_t cmdlen;
+ int count;
+ int key;
+ char pattern[CMDBUF];
+ char addon;
+} cmd;
+
+/* Position of file arguments on argv[] to main() */
+static struct {
+ int first;
+ int current;
+ int last;
+} files;
+
+static void (*oldint) (int); /* old SIGINT handler */
+static void (*oldquit) (int); /* old SIGQUIT handler */
+static void (*oldterm) (int); /* old SIGTERM handler */
+static char *tty; /* result of ttyname(1) */
+static unsigned ontty; /* whether running on tty device */
+static unsigned exitstatus; /* exit status */
+static int pagelen = 23; /* lines on a single screen page */
+static int ttycols = 79; /* screen columns (starting at 0) */
+static struct termios otio; /* old termios settings */
+static int tinfostat = -1; /* terminfo routines initialized */
+static int searchdisplay = TOP; /* matching line position */
+static regex_t re; /* regular expression to search for */
+static int remembered; /* have a remembered search string */
+static int cflag; /* clear screen before each page */
+static int eflag; /* suppress (EOF) */
+static int fflag; /* do not split lines */
+static int nflag; /* no newline for commands required */
+static int rflag; /* "restricted" pg */
+static int sflag; /* use standout mode */
+static const char *pstring = ":"; /* prompt string */
+static char *searchfor; /* search pattern from argv[] */
+static int havepagelen; /* page length is manually defined */
+static long startline; /* start line from argv[] */
+static int nextfile = 1; /* files to advance */
+static jmp_buf jmpenv; /* jump from signal handlers */
+static int canjump; /* jmpenv is valid */
+static wchar_t wbuf[READBUF]; /* used in several widechar routines */
+
+static char *copyright;
+static const char *helpscreen = N_("\
+-------------------------------------------------------\n\
+ h this screen\n\
+ q or Q quit program\n\
+ <newline> next page\n\
+ f skip a page forward\n\
+ d or ^D next halfpage\n\
+ l next line\n\
+ $ last page\n\
+ /regex/ search forward for regex\n\
+ ?regex? or ^regex^ search backward for regex\n\
+ . or ^L redraw screen\n\
+ w or z set page size and go to next page\n\
+ s filename save current file to filename\n\
+ !command shell escape\n\
+ p go to previous file\n\
+ n go to next file\n\
+\n\
+Many commands accept preceding numbers, for example:\n\
++1<newline> (next page); -1<newline> (previous page); 1<newline> (first page).\n\
+\n\
+See pg(1) for more information.\n\
+-------------------------------------------------------\n");
+
+#ifndef HAVE_FSEEKO
+static int fseeko(FILE *f, off_t off, int whence)
+{
+ return fseek(f, (long)off, whence);
+}
+
+static off_t ftello(FILE *f)
+{
+ return (off_t) ftell(f);
+}
+#endif
+
+#ifdef USE_SIGSET /* never defined */
+/* sigset and sigrelse are obsolete - use when POSIX stuff is unavailable */
+# define my_sigset sigset
+# define my_sigrelse sigrelse
+#else
+static int my_sigrelse(int sig)
+{
+ sigset_t sigs;
+
+ if (sigemptyset(&sigs) || sigaddset(&sigs, sig))
+ return -1;
+ return sigprocmask(SIG_UNBLOCK, &sigs, NULL);
+}
+
+typedef void (*my_sighandler_t) (int);
+static my_sighandler_t my_sigset(int sig, my_sighandler_t disp)
+{
+ struct sigaction act, oact;
+
+ act.sa_handler = disp;
+ if (sigemptyset(&act.sa_mask))
+ return SIG_ERR;
+ act.sa_flags = 0;
+ if (sigaction(sig, &act, &oact))
+ return SIG_ERR;
+ if (my_sigrelse(sig))
+ return SIG_ERR;
+ return oact.sa_handler;
+}
+#endif /* USE_SIGSET */
+
+/* Quit pg. */
+static void __attribute__((__noreturn__)) quit(int status)
+{
+ exit(status < 0100 ? status : 077);
+}
+
+/* Usage message and similar routines. */
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] [+line] [+/pattern/] [files]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Browse pagewise through text files.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -number lines per page\n"), out);
+ fputs(_(" -c clear screen before displaying\n"), out);
+ fputs(_(" -e do not pause at end of a file\n"), out);
+ fputs(_(" -f do not split long lines\n"), out);
+ fputs(_(" -n terminate command with new line\n"), out);
+ fputs(_(" -p <prompt> specify prompt\n"), out);
+ fputs(_(" -r disallow shell escape\n"), out);
+ fputs(_(" -s print messages to stdout\n"), out);
+ fputs(_(" +number start at the given line\n"), out);
+ fputs(_(" +/pattern/ start at the line containing pattern\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(16));
+
+ printf(USAGE_MAN_TAIL("pg(1)"));
+ exit(0);
+}
+
+static void __attribute__((__noreturn__)) needarg(const char *s)
+{
+ warnx(_("option requires an argument -- %s"), s);
+ errtryhelp(2);
+}
+
+static void __attribute__((__noreturn__)) invopt(const char *s)
+{
+ warnx(_("illegal option -- %s"), s);
+ errtryhelp(2);
+}
+
+#ifdef HAVE_WIDECHAR
+/* A mbstowcs()-alike function that transparently handles invalid
+ * sequences. */
+static size_t xmbstowcs(wchar_t * pwcs, const char *s, size_t nwcs)
+{
+ size_t n = nwcs;
+ int c;
+
+ ignore_result(mbtowc(pwcs, NULL, MB_CUR_MAX)); /* reset shift state */
+ while (*s && n) {
+ if ((c = mbtowc(pwcs, s, MB_CUR_MAX)) < 0) {
+ s++;
+ *pwcs = L'?';
+ } else
+ s += c;
+ pwcs++;
+ n--;
+ }
+ if (n)
+ *pwcs = L'\0';
+ ignore_result(mbtowc(pwcs, NULL, MB_CUR_MAX));
+ return nwcs - n;
+}
+#endif
+
+/* Helper function for tputs(). */
+static int outcap(int i)
+{
+ char c = i;
+ return write_all(STDOUT_FILENO, &c, 1) == 0 ? 1 : -1;
+}
+
+/* Write messages to terminal. */
+static void mesg(const char *message)
+{
+ if (ontty == 0)
+ return;
+ if (*message != '\n' && sflag)
+ vidputs(A_STANDOUT, outcap);
+ write_all(STDOUT_FILENO, message, strlen(message));
+ if (*message != '\n' && sflag)
+ vidputs(A_NORMAL, outcap);
+}
+
+/* Get the window size. */
+static void getwinsize(void)
+{
+ static int initialized, envlines, envcols, deflines, defcols;
+#ifdef TIOCGWINSZ
+ struct winsize winsz;
+ int badioctl;
+#endif
+ char *p;
+
+ if (initialized == 0) {
+ if ((p = getenv("LINES")) != NULL && *p != '\0')
+ if ((envlines = atoi(p)) < 0)
+ envlines = 0;
+ if ((p = getenv("COLUMNS")) != NULL && *p != '\0')
+ if ((envcols = atoi(p)) < 0)
+ envcols = 0;
+ /* terminfo values. */
+ if (tinfostat != 1 || columns == 0)
+ defcols = 24;
+ else
+ defcols = columns;
+ if (tinfostat != 1 || lines == 0)
+ deflines = 80;
+ else
+ deflines = lines;
+ initialized = 1;
+ }
+#ifdef TIOCGWINSZ
+ badioctl = ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsz);
+#endif
+ if (envcols)
+ ttycols = envcols - 1;
+#ifdef TIOCGWINSZ
+ else if (!badioctl)
+ ttycols = winsz.ws_col - 1;
+#endif
+ else
+ ttycols = defcols - 1;
+ if (havepagelen == 0) {
+ if (envlines)
+ pagelen = envlines - 1;
+#ifdef TIOCGWINSZ
+ else if (!badioctl)
+ pagelen = winsz.ws_row - 1;
+#endif
+ else
+ pagelen = deflines - 1;
+ }
+}
+
+/* Message if skipping parts of files. */
+static void skip(int direction)
+{
+ if (direction > 0)
+ mesg(_("...skipping forward\n"));
+ else
+ mesg(_("...skipping backward\n"));
+}
+
+/* Signal handler while reading from input file. */
+static void sighandler(int signum)
+{
+ if (canjump && (signum == SIGINT || signum == SIGQUIT))
+ longjmp(jmpenv, signum);
+ tcsetattr(STDOUT_FILENO, TCSADRAIN, &otio);
+ quit(exitstatus);
+}
+
+/* Check whether the requested file was specified on the command line. */
+static int checkf(void)
+{
+ if (files.current + nextfile >= files.last) {
+ mesg(_("No next file"));
+ return 1;
+ }
+ if (files.current + nextfile < files.first) {
+ mesg(_("No previous file"));
+ return 1;
+ }
+ return 0;
+}
+
+#ifdef HAVE_WIDECHAR
+/* Return the last character that will fit on the line at col columns in
+ * case MB_CUR_MAX > 1. */
+static char *endline_for_mb(unsigned col, char *s)
+{
+ size_t pos = 0;
+ wchar_t *p = wbuf;
+ wchar_t *end;
+ size_t wl;
+ char *t = s;
+
+ if ((wl = xmbstowcs(wbuf, t, sizeof wbuf - 1)) == (size_t)-1)
+ return s + 1;
+ wbuf[wl] = L'\0';
+ while (*p != L'\0') {
+ switch (*p) {
+ /* Cursor left. */
+ case L'\b':
+ if (pos > 0)
+ pos--;
+ break;
+ /* No cursor movement. */
+ case L'\a':
+ break;
+ /* Special. */
+ case L'\r':
+ pos = 0;
+ break;
+ case L'\n':
+ end = p + 1;
+ goto ended;
+ /* Cursor right. */
+ case L'\t':
+ pos += PG_TABSIZE - (pos % PG_TABSIZE);
+ break;
+ default:
+ if (iswprint(*p))
+ pos += wcwidth(*p);
+ else
+ pos += wcwidth(L'?');
+ }
+ if (pos > col) {
+ if (*p == L'\t')
+ p++;
+ else if (pos > col + 1)
+ /* wcwidth() found a character that has
+ * multiple columns. What happens now?
+ * Assume the terminal will print the
+ * entire character onto the next row. */
+ p--;
+ if (*++p == L'\n')
+ p++;
+ end = p;
+ goto ended;
+ }
+ p++;
+ }
+ end = p;
+ ended:
+ *end = L'\0';
+ p = wbuf;
+ if ((pos = wcstombs(NULL, p, 0)) == (size_t)-1)
+ return s + 1;
+ return s + pos;
+}
+#endif /* HAVE_WIDECHAR */
+
+/* Return the last character that will fit on the line at col columns. */
+static char *endline(unsigned col, char *s)
+{
+ unsigned pos = 0;
+ char *t = s;
+
+#ifdef HAVE_WIDECHAR
+ if (MB_CUR_MAX > 1)
+ return endline_for_mb(col, s);
+#endif
+
+ while (*s != '\0') {
+ switch (*s) {
+ /* Cursor left. */
+ case '\b':
+ if (pos > 0)
+ pos--;
+ break;
+ /* No cursor movement. */
+ case '\a':
+ break;
+ /* Special. */
+ case '\r':
+ pos = 0;
+ break;
+ case '\n':
+ t = s + 1;
+ goto cend;
+ /* Cursor right. */
+ case '\t':
+ pos += PG_TABSIZE - (pos % PG_TABSIZE);
+ break;
+ default:
+ pos++;
+ }
+ if (pos > col) {
+ if (*s == '\t')
+ s++;
+ if (*++s == '\n')
+ s++;
+ t = s;
+ goto cend;
+ }
+ s++;
+ }
+ t = s;
+ cend:
+ return t;
+}
+
+/* Clear the current line on the terminal's screen. */
+static void cline(void)
+{
+ char *buf = xmalloc(ttycols + 2);
+ memset(buf, ' ', ttycols + 2);
+ buf[0] = '\r';
+ buf[ttycols + 1] = '\r';
+ write_all(STDOUT_FILENO, buf, ttycols + 2);
+ free(buf);
+}
+
+/* Evaluate a command character's semantics. */
+static int getstate(int c)
+{
+ switch (c) {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ case '\0':
+ return COUNT;
+ case '-':
+ case '+':
+ return SIGN;
+ case 'l':
+ case 'd':
+ case '\004':
+ case 'f':
+ case 'z':
+ case '.':
+ case '\014':
+ case '$':
+ case 'n':
+ case 'p':
+ case 'w':
+ case 'h':
+ case 'q':
+ case 'Q':
+ return CMD_FIN;
+ case '/':
+ case '?':
+ case '^':
+ return SEARCH;
+ case 's':
+ case '!':
+ return STRING;
+ case 'm':
+ case 'b':
+ case 't':
+ return ADDON_FIN;
+ default:
+#ifdef PG_BELL
+ if (bell)
+ tputs(bell, STDOUT_FILENO, outcap);
+#endif
+ return INVALID;
+ }
+}
+
+/* Get the count and ignore last character of string. */
+static int getcount(char *cmdstr)
+{
+ char *buf;
+ char *p;
+ int i;
+
+ if (*cmdstr == '\0')
+ return 1;
+ buf = xmalloc(strlen(cmdstr) + 1);
+ strcpy(buf, cmdstr);
+ if (cmd.key != '\0') {
+ if (cmd.key == '/' || cmd.key == '?' || cmd.key == '^') {
+ if ((p = strchr(buf, cmd.key)) != NULL)
+ *p = '\0';
+ } else
+ *(buf + strlen(buf) - 1) = '\0';
+ }
+ if (*buf == '\0') {
+ free(buf);
+ return 1;
+ }
+ if (buf[0] == '-' && buf[1] == '\0') {
+ i = -1;
+ } else {
+ if (*buf == '+')
+ i = atoi(buf + 1);
+ else
+ i = atoi(buf);
+ }
+ free(buf);
+ return i;
+}
+
+/* Read what the user writes at the prompt. This is tricky because we
+ * check for valid input. */
+static void prompt(long long pageno)
+{
+ struct termios tio;
+ char key;
+ int state = COUNT;
+ int escape = 0;
+ char b[LINE_MAX], *p;
+
+ if (pageno != -1) {
+ if ((p = strstr(pstring, "%d")) == NULL) {
+ mesg(pstring);
+ } else {
+ strcpy(b, pstring);
+ sprintf(b + (p - pstring), "%lld", pageno);
+ strcat(b, p + 2);
+ mesg(b);
+ }
+ }
+ cmd.key = cmd.addon = cmd.cmdline[0] = '\0';
+ cmd.cmdlen = 0;
+ tcgetattr(STDOUT_FILENO, &tio);
+ tio.c_lflag &= ~(ICANON | ECHO);
+ tio.c_cc[VMIN] = 1;
+ tio.c_cc[VTIME] = 0;
+ tcsetattr(STDOUT_FILENO, TCSADRAIN, &tio);
+ tcflush(STDOUT_FILENO, TCIFLUSH);
+ for (;;) {
+ switch (read(STDOUT_FILENO, &key, 1)) {
+ case 0:
+ quit(0);
+ /* NOTREACHED */
+ case -1:
+ quit(1);
+ }
+ if (key == tio.c_cc[VERASE]) {
+ if (cmd.cmdlen) {
+ write_all(STDOUT_FILENO, "\b \b", 3);
+ cmd.cmdline[--cmd.cmdlen] = '\0';
+ switch (state) {
+ case ADDON_FIN:
+ state = SEARCH_FIN;
+ cmd.addon = '\0';
+ break;
+ case CMD_FIN:
+ cmd.key = '\0';
+ state = COUNT;
+ break;
+ case SEARCH_FIN:
+ state = SEARCH;
+ /* fallthrough */
+ case SEARCH:
+ if (cmd.cmdline[cmd.cmdlen - 1] == '\\') {
+ escape = 1;
+ while (cmd.cmdline[cmd.cmdlen
+ - escape - 1]
+ == '\\')
+ escape++;
+ escape %= 2;
+ } else {
+ escape = 0;
+ if (strchr(cmd.cmdline, cmd.key)
+ == NULL) {
+ cmd.key = '\0';
+ state = COUNT;
+ }
+ }
+ break;
+ }
+ }
+ if (cmd.cmdlen == 0) {
+ state = COUNT;
+ cmd.key = '\0';
+ }
+ continue;
+ }
+ if (key == tio.c_cc[VKILL]) {
+ cline();
+ cmd.cmdlen = 0;
+ cmd.cmdline[0] = '\0';
+ state = COUNT;
+ cmd.key = '\0';
+ continue;
+ }
+ if (key == '\n' || (nflag && state == COUNT && key == ' '))
+ break;
+ if (cmd.cmdlen >= CMDBUF - 1)
+ continue;
+ switch (state) {
+ case STRING:
+ break;
+ case SEARCH:
+ if (!escape) {
+ if (key == cmd.key)
+ state = SEARCH_FIN;
+ if (key == '\\')
+ escape = 1;
+ } else
+ escape = 0;
+ break;
+ case SEARCH_FIN:
+ if (getstate(key) != ADDON_FIN)
+ continue;
+ state = ADDON_FIN;
+ cmd.addon = key;
+ switch (key) {
+ case 't':
+ searchdisplay = TOP;
+ break;
+ case 'm':
+ searchdisplay = MIDDLE;
+ break;
+ case 'b':
+ searchdisplay = BOTTOM;
+ break;
+ }
+ break;
+ case CMD_FIN:
+ case ADDON_FIN:
+ continue;
+ default:
+ state = getstate(key);
+ switch (state) {
+ case SIGN:
+ if (cmd.cmdlen != 0) {
+ state = INVALID;
+ continue;
+ }
+ state = COUNT;
+ /* fallthrough */
+ case COUNT:
+ break;
+ case ADDON_FIN:
+ case INVALID:
+ continue;
+ default:
+ cmd.key = key;
+ }
+ }
+ write_all(STDOUT_FILENO, &key, 1);
+ cmd.cmdline[cmd.cmdlen++] = key;
+ cmd.cmdline[cmd.cmdlen] = '\0';
+ if (nflag && state == CMD_FIN)
+ goto endprompt;
+ }
+ endprompt:
+ tcsetattr(STDOUT_FILENO, TCSADRAIN, &otio);
+ cline();
+ cmd.count = getcount(cmd.cmdline);
+}
+
+#ifdef HAVE_WIDECHAR
+/* Remove backspace formatting, for searches in case MB_CUR_MAX > 1. */
+static char *colb_for_mb(char *s)
+{
+ char *p = s;
+ wchar_t *wp, *wq;
+ size_t l = strlen(s), wl;
+ unsigned i;
+
+ if ((wl = xmbstowcs(wbuf, p, sizeof wbuf)) == (size_t)-1)
+ return s;
+ for (wp = wbuf, wq = wbuf, i = 0; *wp != L'\0' && i < wl; wp++, wq++) {
+ if (*wp == L'\b') {
+ if (wq != wbuf)
+ wq -= 2;
+ else
+ wq--;
+ } else
+ *wq = *wp;
+ }
+ *wq = L'\0';
+ wp = wbuf;
+ wcstombs(s, wp, l + 1);
+
+ return s;
+}
+#endif
+
+/* Remove backspace formatting, for searches. */
+static char *colb(char *s)
+{
+ char *p = s, *q;
+
+#ifdef HAVE_WIDECHAR
+ if (MB_CUR_MAX > 1)
+ return colb_for_mb(s);
+#endif
+
+ for (q = s; *p != '\0'; p++, q++) {
+ if (*p == '\b') {
+ if (q != s)
+ q -= 2;
+ else
+ q--;
+ } else
+ *q = *p;
+ }
+ *q = '\0';
+
+ return s;
+}
+
+#ifdef HAVE_WIDECHAR
+/* Convert non-printable characters to spaces in case MB_CUR_MAX > 1. */
+static void makeprint_for_mb(char *s, size_t l)
+{
+ char *t = s;
+ wchar_t *wp = wbuf;
+ size_t wl;
+
+ if ((wl = xmbstowcs(wbuf, t, sizeof wbuf)) == (size_t)-1)
+ return;
+ while (wl--) {
+ if (!iswprint(*wp) && *wp != L'\n' && *wp != L'\r'
+ && *wp != L'\b' && *wp != L'\t')
+ *wp = L'?';
+ wp++;
+ }
+ wp = wbuf;
+ wcstombs(s, wp, l);
+}
+#endif
+
+/* Convert non-printable characters to spaces. */
+static void makeprint(char *s, size_t l)
+{
+#ifdef HAVE_WIDECHAR
+ if (MB_CUR_MAX > 1) {
+ makeprint_for_mb(s, l);
+ return;
+ }
+#endif
+
+ while (l--) {
+ if (!isprint(cuc(*s)) && *s != '\n' && *s != '\r'
+ && *s != '\b' && *s != '\t')
+ *s = '?';
+ s++;
+ }
+}
+
+/* Strip backslash characters from the given string. */
+static void striprs(char *s)
+{
+ char *p = s;
+
+ do {
+ if (*s == '\\') {
+ s++;
+ }
+ *p++ = *s;
+ } while (*s++ != '\0');
+}
+
+/* Extract the search pattern off the command line. */
+static char *makepat(void)
+{
+ char *p;
+
+ if (cmd.addon == '\0')
+ p = cmd.cmdline + strlen(cmd.cmdline) - 1;
+ else
+ p = cmd.cmdline + strlen(cmd.cmdline) - 2;
+ if (*p == cmd.key)
+ *p = '\0';
+ else
+ *(p + 1) = '\0';
+ if ((p = strchr(cmd.cmdline, cmd.key)) != NULL) {
+ p++;
+ striprs(p);
+ }
+ return p;
+}
+
+/* Process errors that occurred in temporary file operations. */
+static void __attribute__((__noreturn__)) tmperr(FILE *f, const char *ftype)
+{
+ if (ferror(f))
+ warn(_("Read error from %s file"), ftype);
+ else if (feof(f))
+ /* Most likely '\0' in input. */
+ warnx(_("Unexpected EOF in %s file"), ftype);
+ else
+ warn(_("Unknown error in %s file"), ftype);
+ quit(++exitstatus);
+}
+
+/* Read the file and respond to user input. Beware: long and ugly. */
+static void pgfile(FILE *f, const char *name)
+{
+ off_t pos, oldpos, fpos;
+ /* These are the line counters:
+ * line the line desired to display
+ * fline the current line of the input file
+ * bline the current line of the file buffer
+ * oldline the line before a search was started
+ * eofline the last line of the file if it is already reached
+ * dline the line on the display */
+ off_t line = 0, fline = 0, bline = 0, oldline = 0, eofline = 0;
+ int dline = 0;
+ int search = 0;
+ unsigned searchcount = 0;
+ /* Advance to EOF immediately. */
+ int seekeof = 0;
+ /* EOF has been reached by `line'. */
+ int eof = 0;
+ /* f and fbuf refer to the same file. */
+ int nobuf = 0;
+ int sig;
+ int rerror;
+ size_t sz;
+ char b[READBUF + 1];
+ char *p;
+ /* fbuf an exact copy of the input file as it gets read
+ * find index table for input, one entry per line
+ * save for the s command, to save to a file */
+ FILE *fbuf, *find, *save;
+
+ if (ontty == 0) {
+ /* Just copy stdin to stdout. */
+ while ((sz = fread(b, sizeof *b, READBUF, f)) != 0)
+ write_all(STDOUT_FILENO, b, sz);
+ if (ferror(f)) {
+ warn("%s", name);
+ exitstatus++;
+ }
+ return;
+ }
+ if ((fpos = fseeko(f, (off_t)0, SEEK_SET)) == -1)
+ fbuf = tmpfile();
+ else {
+ fbuf = f;
+ nobuf = 1;
+ }
+ find = tmpfile();
+ if (fbuf == NULL || find == NULL) {
+ warn(_("Cannot create temporary file"));
+ quit(++exitstatus);
+ }
+ if (searchfor) {
+ search = FORWARD;
+ oldline = 0;
+ searchcount = 1;
+ rerror = regcomp(&re, searchfor, REG_NOSUB | REG_NEWLINE);
+ if (rerror != 0) {
+ mesg(_("RE error: "));
+ regerror(rerror, &re, b, READBUF);
+ mesg(b);
+ goto newcmd;
+ }
+ remembered = 1;
+ }
+
+ for (line = startline;;) {
+ /* Get a line from input file or buffer. */
+ if (line < bline) {
+ fseeko(find, line * sizeof pos, SEEK_SET);
+ if (fread(&pos, sizeof pos, 1, find) == 0)
+ tmperr(find, "index");
+ fseeko(find, (off_t)0, SEEK_END);
+ fseeko(fbuf, pos, SEEK_SET);
+ if (fgets(b, READBUF, fbuf) == NULL)
+ tmperr(fbuf, "buffer");
+ } else if (eofline == 0) {
+ fseeko(find, (off_t)0, SEEK_END);
+ do {
+ if (!nobuf)
+ fseeko(fbuf, (off_t)0, SEEK_END);
+ pos = ftello(fbuf);
+ if ((sig = setjmp(jmpenv)) != 0) {
+ /* We got a signal. */
+ canjump = 0;
+ my_sigrelse(sig);
+ fseeko(fbuf, pos, SEEK_SET);
+ *b = '\0';
+ dline = pagelen;
+ break;
+ }
+
+ if (nobuf)
+ fseeko(f, fpos, SEEK_SET);
+ canjump = 1;
+ p = fgets(b, READBUF, f);
+ if (nobuf)
+ if ((fpos = ftello(f)) == -1)
+ warn("%s", name);
+ canjump = 0;
+
+ if (p == NULL || *b == '\0') {
+ if (ferror(f))
+ warn("%s", name);
+ eofline = fline;
+ eof = 1;
+ break;
+ }
+
+ if (!nobuf)
+ fputs(b, fbuf);
+ fwrite_all(&pos, sizeof pos, 1, find);
+ if (!fflag) {
+ oldpos = pos;
+ p = b;
+ while (*(p = endline(ttycols,
+ p))
+ != '\0') {
+ pos = oldpos + (p - b);
+ fwrite_all(&pos,
+ sizeof pos,
+ 1, find);
+ fline++;
+ bline++;
+ }
+ }
+ fline++;
+ } while (line > bline++);
+ } else {
+ /* eofline != 0 */
+ eof = 1;
+ }
+ if (search == FORWARD && remembered == 1) {
+ if (eof) {
+ line = oldline;
+ search = searchcount = 0;
+ mesg(_("Pattern not found"));
+ eof = 0;
+ goto newcmd;
+ }
+ line++;
+ colb(b);
+ if (regexec(&re, b, 0, NULL, 0) == 0) {
+ searchcount--;
+ }
+ if (searchcount == 0) {
+ search = dline = 0;
+ switch (searchdisplay) {
+ case TOP:
+ line -= 1;
+ break;
+ case MIDDLE:
+ line -= pagelen / 2 + 1;
+ break;
+ case BOTTOM:
+ line -= pagelen;
+ break;
+ }
+ skip(1);
+ }
+ continue;
+ }
+
+ if (eof) {
+ /* We are not searching. */
+ line = bline;
+ } else if (*b != '\0') {
+ if (cflag && clear_screen) {
+ switch (dline) {
+ case 0:
+ tputs(clear_screen, STDOUT_FILENO,
+ outcap);
+ dline = 0;
+ }
+ }
+ line++;
+ if (eofline && line == eofline)
+ eof = 1;
+ dline++;
+ if ((sig = setjmp(jmpenv)) != 0) {
+ /* We got a signal. */
+ canjump = 0;
+ my_sigrelse(sig);
+ dline = pagelen;
+ } else {
+ p = endline(ttycols, b);
+ sz = p - b;
+ makeprint(b, sz);
+ canjump = 1;
+ write_all(STDOUT_FILENO, b, sz);
+ canjump = 0;
+ }
+ }
+ if (dline >= pagelen || eof) {
+ /* Time for prompting! */
+ if (eof && seekeof) {
+ eof = seekeof = 0;
+ if (line >= pagelen)
+ line -= pagelen;
+ else
+ line = 0;
+ dline = -1;
+ continue;
+ }
+ newcmd:
+ if (eof) {
+ if (fline == 0 || eflag)
+ break;
+ mesg(_("(EOF)"));
+ }
+ prompt((line - 1) / pagelen + 1);
+ switch (cmd.key) {
+ case '/':
+ /* Search forward. */
+ search = FORWARD;
+ oldline = line;
+ searchcount = cmd.count;
+ p = makepat();
+ if (p != NULL && *p) {
+ if (remembered == 1)
+ regfree(&re);
+ rerror = regcomp(&re, p,
+ REG_NOSUB |
+ REG_NEWLINE);
+ if (rerror != 0) {
+ mesg(_("RE error: "));
+ sz = regerror(rerror, &re,
+ b, READBUF);
+ mesg(b);
+ goto newcmd;
+ }
+ remembered = 1;
+ } else if (remembered == 0) {
+ mesg(_("No remembered search string"));
+ goto newcmd;
+ }
+ continue;
+ case '?':
+ case '^':
+ /* Search backward. */
+ search = BACKWARD;
+ oldline = line;
+ searchcount = cmd.count;
+ p = makepat();
+ if (p != NULL && *p) {
+ if (remembered == 1)
+ regfree(&re);
+ rerror = regcomp(&re, p,
+ REG_NOSUB |
+ REG_NEWLINE);
+ if (rerror != 0) {
+ mesg(_("RE error: "));
+ regerror(rerror, &re,
+ b, READBUF);
+ mesg(b);
+ goto newcmd;
+ }
+ remembered = 1;
+ } else if (remembered == 0) {
+ mesg(_("No remembered search string"));
+ goto newcmd;
+ }
+ line -= pagelen;
+ if (line <= 0)
+ goto notfound_bw;
+ while (line) {
+ fseeko(find, --line * sizeof pos,
+ SEEK_SET);
+ if (fread(&pos, sizeof pos, 1, find) ==
+ 0)
+ tmperr(find, "index");
+ fseeko(find, (off_t)0, SEEK_END);
+ fseeko(fbuf, pos, SEEK_SET);
+ if (fgets(b, READBUF, fbuf) == NULL)
+ tmperr(fbuf, "buffer");
+ colb(b);
+ if (regexec(&re, b, 0, NULL, 0) == 0)
+ searchcount--;
+ if (searchcount == 0)
+ goto found_bw;
+ }
+ notfound_bw:
+ line = oldline;
+ search = searchcount = 0;
+ mesg(_("Pattern not found"));
+ goto newcmd;
+ found_bw:
+ eof = search = dline = 0;
+ skip(-1);
+ switch (searchdisplay) {
+ case TOP:
+ /* line -= 1; */
+ break;
+ case MIDDLE:
+ line -= pagelen / 2;
+ break;
+ case BOTTOM:
+ if (line != 0)
+ dline = -1;
+ line -= pagelen;
+ break;
+ }
+ if (line < 0)
+ line = 0;
+ continue;
+ case 's':
+ /* Save to file. */
+ p = cmd.cmdline;
+ while (*++p == ' ') ;
+ if (*p == '\0')
+ goto newcmd;
+ save = fopen(p, "wb");
+ if (save == NULL) {
+ cmd.count = errno;
+ mesg(_("cannot open "));
+ mesg(p);
+ mesg(": ");
+ mesg(strerror(cmd.count));
+ goto newcmd;
+ }
+ /* Advance to EOF. */
+ fseeko(find, (off_t)0, SEEK_END);
+ for (;;) {
+ if (!nobuf)
+ fseeko(fbuf, (off_t)0,
+ SEEK_END);
+ pos = ftello(fbuf);
+ if (fgets(b, READBUF, f) == NULL) {
+ eofline = fline;
+ break;
+ }
+ if (!nobuf)
+ fputs(b, fbuf);
+ fwrite_all(&pos, sizeof pos, 1, find);
+ if (!fflag) {
+ oldpos = pos;
+ p = b;
+ while (*(p = endline(ttycols,
+ p))
+ != '\0') {
+ pos = oldpos + (p - b);
+ fwrite_all(&pos,
+ sizeof pos,
+ 1, find);
+ fline++;
+ bline++;
+ }
+ }
+ fline++;
+ bline++;
+ }
+ fseeko(fbuf, (off_t)0, SEEK_SET);
+ while ((sz = fread(b, sizeof *b, READBUF,
+ fbuf)) != 0) {
+ /* No error check for compat. */
+ fwrite_all(b, sizeof *b, sz, save);
+ }
+ if (close_stream(save) != 0) {
+ cmd.count = errno;
+ mesg(_("write failed"));
+ mesg(": ");
+ mesg(p);
+ mesg(strerror(cmd.count));
+ goto newcmd;
+ }
+ fseeko(fbuf, (off_t)0, SEEK_END);
+ mesg(_("saved"));
+ goto newcmd;
+ case 'l':
+ /* Next line. */
+ if (*cmd.cmdline != 'l')
+ eof = 0;
+ if (cmd.count == 0)
+ cmd.count = 1; /* compat */
+ if (isdigit(cuc(*cmd.cmdline))) {
+ line = cmd.count - 2;
+ dline = 0;
+ } else {
+ if (cmd.count != 1) {
+ line += cmd.count - 1 - pagelen;
+ dline = -1;
+ skip(cmd.count);
+ }
+ /* Nothing to do if (count == 1) */
+ }
+ break;
+ case 'd':
+ /* Half screen forward. */
+ case '\004': /* ^D */
+ if (*cmd.cmdline != cmd.key)
+ eof = 0;
+ if (cmd.count == 0)
+ cmd.count = 1; /* compat */
+ line += (cmd.count * pagelen / 2)
+ - pagelen - 1;
+ dline = -1;
+ skip(cmd.count);
+ break;
+ case 'f':
+ /* Skip forward. */
+ if (cmd.count <= 0)
+ cmd.count = 1; /* compat */
+ line += cmd.count * pagelen - 2;
+ if (eof)
+ line += 2;
+ if (*cmd.cmdline != 'f')
+ eof = 0;
+ else if (eof)
+ break;
+ if (eofline && line >= eofline)
+ line -= pagelen;
+ dline = -1;
+ skip(cmd.count);
+ break;
+ case '\0':
+ /* Just a number, or '-', or <newline>. */
+ if (cmd.count == 0)
+ cmd.count = 1; /* compat */
+ if (isdigit(cuc(*cmd.cmdline)))
+ line = (cmd.count - 1) * pagelen - 2;
+ else
+ line += (cmd.count - 1)
+ * (pagelen - 1) - 2;
+ if (*cmd.cmdline != '\0')
+ eof = 0;
+ if (cmd.count != 1) {
+ skip(cmd.count);
+ dline = -1;
+ } else {
+ dline = 1;
+ line += 2;
+ }
+ break;
+ case '$':
+ /* Advance to EOF. */
+ if (!eof)
+ skip(1);
+ eof = 0;
+ line = LONG_MAX;
+ seekeof = 1;
+ dline = -1;
+ break;
+ case '.':
+ case '\014': /* ^L */
+ /* Repaint screen. */
+ eof = 0;
+ if (line >= pagelen)
+ line -= pagelen;
+ else
+ line = 0;
+ dline = 0;
+ break;
+ case '!':
+ /* Shell escape. */
+ if (rflag) {
+ mesg(program_invocation_short_name);
+ mesg(_(": !command not allowed in "
+ "rflag mode.\n"));
+ } else {
+ pid_t cpid;
+
+ write_all(STDOUT_FILENO, cmd.cmdline,
+ strlen(cmd.cmdline));
+ write_all(STDOUT_FILENO, "\n", 1);
+ my_sigset(SIGINT, SIG_IGN);
+ my_sigset(SIGQUIT, SIG_IGN);
+ switch (cpid = fork()) {
+ case 0:
+ {
+ const char *sh = getenv("SHELL");
+ if (!sh)
+ sh = "/bin/sh";
+ if (!nobuf)
+ fclose(fbuf);
+ fclose(find);
+ if (isatty(0) == 0) {
+ close(0);
+ open(tty, O_RDONLY);
+ } else {
+ fclose(f);
+ }
+ my_sigset(SIGINT, oldint);
+ my_sigset(SIGQUIT, oldquit);
+ my_sigset(SIGTERM, oldterm);
+ execl(sh, sh, "-c",
+ cmd.cmdline + 1, (char *)NULL);
+ errexec(sh);
+ break;
+ }
+ case -1:
+ mesg(_("fork() failed, "
+ "try again later\n"));
+ break;
+ default:
+ while (wait(NULL) != cpid) ;
+ }
+ my_sigset(SIGINT, sighandler);
+ my_sigset(SIGQUIT, sighandler);
+ mesg("!\n");
+ }
+ goto newcmd;
+ case 'h':
+ {
+ /* Help! */
+ const char *help = _(helpscreen);
+ write_all(STDOUT_FILENO, copyright,
+ strlen(copyright));
+ write_all(STDOUT_FILENO, help,
+ strlen(help));
+ goto newcmd;
+ }
+ case 'n':
+ /* Next file. */
+ if (cmd.count == 0)
+ cmd.count = 1;
+ nextfile = cmd.count;
+ if (checkf()) {
+ nextfile = 1;
+ goto newcmd;
+ }
+ eof = 1;
+ break;
+ case 'p':
+ /* Previous file. */
+ if (cmd.count == 0)
+ cmd.count = 1;
+ nextfile = 0 - cmd.count;
+ if (checkf()) {
+ nextfile = 1;
+ goto newcmd;
+ }
+ eof = 1;
+ break;
+ case 'q':
+ case 'Q':
+ /* Exit pg. */
+ quit(exitstatus);
+ /* NOTREACHED */
+ case 'w':
+ case 'z':
+ /* Set window size. */
+ if (cmd.count < 0)
+ cmd.count = 0;
+ if (*cmd.cmdline != cmd.key)
+ pagelen = ++cmd.count;
+ dline = 1;
+ break;
+ }
+ if (line <= 0) {
+ line = 0;
+ dline = 0;
+ }
+ if (cflag && dline == 1) {
+ dline = 0;
+ line--;
+ }
+ }
+ if (eof)
+ break;
+ }
+ fclose(find);
+ if (!nobuf)
+ fclose(fbuf);
+}
+
+static int parse_arguments(int arg, int argc, char **argv)
+{
+ FILE *input;
+
+ files.first = arg;
+ files.last = arg + argc - 1;
+ for (; argv[arg]; arg += nextfile) {
+ nextfile = 1;
+ files.current = arg;
+ if (argc > 2) {
+ static int firsttime;
+ firsttime++;
+ if (firsttime > 1) {
+ mesg(_("(Next file: "));
+ mesg(argv[arg]);
+ mesg(")");
+ newfile:
+ if (ontty) {
+ prompt(-1);
+ switch (cmd.key) {
+ case 'n':
+ /* Next file. */
+ if (cmd.count == 0)
+ cmd.count = 1;
+ nextfile = cmd.count;
+ if (checkf()) {
+ nextfile = 1;
+ mesg(":");
+ goto newfile;
+ }
+ continue;
+ case 'p':
+ /* Previous file. */
+ if (cmd.count == 0)
+ cmd.count = 1;
+ nextfile = 0 - cmd.count;
+ if (checkf()) {
+ nextfile = 1;
+ mesg(":");
+ goto newfile;
+ }
+ continue;
+ case 'q':
+ case 'Q':
+ quit(exitstatus);
+ }
+ } else
+ mesg("\n");
+ }
+ }
+ if (strcmp(argv[arg], "-") == 0)
+ input = stdin;
+ else {
+ input = fopen(argv[arg], "r");
+ if (input == NULL) {
+ warn("%s", argv[arg]);
+ exitstatus++;
+ continue;
+ }
+ }
+ if (ontty == 0 && argc > 2) {
+ /* Use the prefix as specified by SUSv2. */
+ write_all(STDOUT_FILENO, "::::::::::::::\n", 15);
+ write_all(STDOUT_FILENO, argv[arg], strlen(argv[arg]));
+ write_all(STDOUT_FILENO, "\n::::::::::::::\n", 16);
+ }
+ pgfile(input, argv[arg]);
+ if (input != stdin)
+ fclose(input);
+ }
+ return exitstatus;
+}
+
+int main(int argc, char **argv)
+{
+ int arg, i;
+ char *p;
+
+ xasprintf(&copyright,
+ _("%s %s Copyright (c) 2000-2001 Gunnar Ritter. All rights reserved.\n"),
+ program_invocation_short_name, PACKAGE_VERSION);
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ if (tcgetattr(STDOUT_FILENO, &otio) == 0) {
+ ontty = 1;
+ oldint = my_sigset(SIGINT, sighandler);
+ oldquit = my_sigset(SIGQUIT, sighandler);
+ oldterm = my_sigset(SIGTERM, sighandler);
+ setlocale(LC_CTYPE, "");
+ setlocale(LC_COLLATE, "");
+ tty = ttyname(STDOUT_FILENO);
+ setupterm(NULL, STDOUT_FILENO, &tinfostat);
+ getwinsize();
+ helpscreen = _(helpscreen);
+ }
+ for (arg = 1; argv[arg]; arg++) {
+ if (*argv[arg] == '+')
+ continue;
+ if (*argv[arg] != '-' || argv[arg][1] == '\0')
+ break;
+ argc--;
+
+ if (!strcmp(argv[arg], "--help")) {
+ usage();
+ }
+
+ if (!strcmp(argv[arg], "--version")) {
+ print_version(EXIT_SUCCESS);
+ return EXIT_SUCCESS;
+ }
+
+ for (i = 1; argv[arg][i]; i++) {
+ switch (argv[arg][i]) {
+ case '-':
+ if (i != 1 || argv[arg][i + 1])
+ invopt(&argv[arg][i]);
+ goto endargs;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ pagelen = strtol_or_err(argv[arg] + 1,
+ _("failed to parse number of lines per page"));
+ havepagelen = 1;
+ goto nextarg;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'p':
+ if (argv[arg][i + 1]) {
+ pstring = &argv[arg][i + 1];
+ } else if (argv[++arg]) {
+ --argc;
+ pstring = argv[arg];
+ } else
+ needarg("-p");
+ goto nextarg;
+ case 'r':
+ rflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ invopt(&argv[arg][i]);
+ }
+ }
+ nextarg:
+ ;
+ }
+ endargs:
+ for (arg = 1; argv[arg]; arg++) {
+ if (*argv[arg] == '-') {
+ if (argv[arg][1] == '-') {
+ arg++;
+ break;
+ }
+ if (argv[arg][1] == '\0')
+ break;
+ if (argv[arg][1] == 'p' && argv[arg][2] == '\0')
+ arg++;
+ continue;
+ }
+ if (*argv[arg] != '+')
+ break;
+ argc--;
+ switch (*(argv[arg] + 1)) {
+ case '\0':
+ needarg("+");
+ /*NOTREACHED*/
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ startline = strtol_or_err(argv[arg] + 1,
+ _("failed to parse number of lines per page"));
+ break;
+ case '/':
+ searchfor = argv[arg] + 2;
+ if (*searchfor == '\0')
+ needarg("+/");
+ p = searchfor + strlen(searchfor) - 1;
+ if (*p == '/')
+ *p = '\0';
+ if (*searchfor == '\0')
+ needarg("+/");
+ break;
+ default:
+ invopt(argv[arg]);
+ }
+ }
+ if (argc == 1)
+ pgfile(stdin, "stdin");
+ else
+ exitstatus = parse_arguments(arg, argc, argv);
+
+ quit(exitstatus);
+ /* NOTREACHED */
+ return 0;
+}
diff --git a/text-utils/rev.1 b/text-utils/rev.1
new file mode 100644
index 0000000..3b45bfa
--- /dev/null
+++ b/text-utils/rev.1
@@ -0,0 +1,63 @@
+.\" Copyright (c) 1985, 1992 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)rev.1 6.3 (Berkeley) 3/21/92
+.\"
+.TH REV "1" "September 2011" "util-linux" "User Commands"
+.SH NAME
+rev \- reverse lines characterwise
+.SH SYNOPSIS
+.B rev
+[option]
+.RI [ file ...]
+.SH DESCRIPTION
+The
+.B rev
+utility copies the specified files to standard output, reversing the order of
+characters in every line. If no files are specified, standard input is read.
+.PP
+This utility is a line-oriented tool and it uses in-memory allocated buffer
+for a whole wide-char line. If the input file is huge and without line breaks
+than allocate the memory for the file may be unsuccessful.
+.SH OPTIONS
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH SEE ALSO
+.BR tac (1)
+.SH AVAILABILITY
+The rev command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/text-utils/rev.c b/text-utils/rev.c
new file mode 100644
index 0000000..133d813
--- /dev/null
+++ b/text-utils/rev.c
@@ -0,0 +1,186 @@
+/*-
+ * Copyright (c) 1987, 1992 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ *
+ * Modified for Linux by Charles Hannum (mycroft@gnu.ai.mit.edu)
+ * and Brian Koehmstedt (bpk@gnu.ai.mit.edu)
+ *
+ * Wed Sep 14 22:26:00 1994: Patch from bjdouma <bjdouma@xs4all.nl> to handle
+ * last line that has no newline correctly.
+ * 3-Jun-1998: Patched by Nicolai Langfeldt to work better on Linux:
+ * Handle any-length-lines. Code copied from util-linux' setpwnam.c
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * added Native Language Support
+ * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
+ * modified to work correctly in multi-byte locales
+ * July 2010 - Davidlohr Bueso <dave@gnu.org>
+ * Fixed memory leaks (including Linux signal handling)
+ * Added some memory allocation error handling
+ * Lowered the default buffer size to 256, instead of 512 bytes
+ * Changed tab indentation to 8 chars for better reading the code
+ */
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <getopt.h>
+
+#include "nls.h"
+#include "xalloc.h"
+#include "widechar.h"
+#include "c.h"
+#include "closestream.h"
+
+static void sig_handler(int signo __attribute__ ((__unused__)))
+{
+ _exit(EXIT_SUCCESS);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fprintf(out, _("Usage: %s [options] [file ...]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Reverse lines characterwise.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("rev(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static void reverse_str(wchar_t *str, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n / 2; ++i) {
+ wchar_t tmp = str[i];
+ str[i] = str[n - 1 - i];
+ str[n - 1 - i] = tmp;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ char const *filename = "stdin";
+ wchar_t *buf;
+ size_t len, bufsiz = BUFSIZ;
+ FILE *fp = stdin;
+ int ch, rval = EXIT_SUCCESS;
+ uintmax_t line;
+
+ static const struct option longopts[] = {
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch(ch) {
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ buf = xmalloc(bufsiz * sizeof(wchar_t));
+
+ do {
+ if (*argv) {
+ if ((fp = fopen(*argv, "r")) == NULL) {
+ warn(_("cannot open %s"), *argv );
+ rval = EXIT_FAILURE;
+ ++argv;
+ continue;
+ }
+ filename = *argv++;
+ }
+
+ line = 0;
+ while (fgetws(buf, bufsiz, fp)) {
+ len = wcslen(buf);
+
+ if (len == 0)
+ continue;
+
+ /* This is my hack from setpwnam.c -janl */
+ while (buf[len-1] != '\n' && !feof(fp)) {
+ /* Extend input buffer if it failed getting the whole line */
+ /* So now we double the buffer size */
+ bufsiz *= 2;
+
+ buf = xrealloc(buf, bufsiz * sizeof(wchar_t));
+
+ /* And fill the rest of the buffer */
+ if (!fgetws(&buf[len], bufsiz/2, fp))
+ break;
+
+ len = wcslen(buf);
+ }
+ if (buf[len - 1] == '\n')
+ buf[len--] = '\0';
+ reverse_str(buf, len);
+ fputws(buf, stdout);
+ line++;
+ }
+ if (ferror(fp)) {
+ warn("%s: %ju", filename, line);
+ rval = EXIT_FAILURE;
+ }
+ if (fp != stdin)
+ fclose(fp);
+ } while(*argv);
+
+ free(buf);
+ return rval;
+}
+
diff --git a/text-utils/ul.1 b/text-utils/ul.1
new file mode 100644
index 0000000..8600875
--- /dev/null
+++ b/text-utils/ul.1
@@ -0,0 +1,113 @@
+.\" Copyright (c) 1980, 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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.
+.\"
+.\" @(#)ul.1 8.1 (Berkeley) 6/6/93
+.\"
+.TH UL "1" "September 2011" "util-linux" "User Commands"
+.SH NAME
+ul \- do underlining
+.SH SYNOPSIS
+.BR ul " [options]"
+.RI [ file ...]
+.SH DESCRIPTION
+.B ul
+reads the named files (or standard input if none are given) and translates
+occurrences of underscores to the sequence which indicates underlining for
+the terminal in use, as specified by the environment variable
+.BR TERM .
+The
+.I terminfo
+database is read to determine the appropriate sequences for underlining. If
+the terminal is incapable of underlining but is capable of a standout mode,
+then that is used instead. If the terminal can overstrike, or handles
+underlining automatically,
+.B ul
+degenerates to
+.BR cat (1).
+If the terminal cannot underline, underlining is ignored.
+.SH OPTIONS
+.TP
+\fB\-i\fR, \fB\-\-indicated\fR
+Underlining is indicated by a separate line containing appropriate dashes
+`\-'; this is useful when you want to look at the underlining which is
+present in an
+.B nroff
+output stream on a crt-terminal.
+.TP
+\fB\-t\fR, \fB\-T\fR, \fB\-\-terminal\fR \fIterminal\fR
+Override the environment variable
+.B TERM
+with the specified
+.I terminal
+type.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH ENVIRONMENT
+The following environment variable is used:
+.TP
+.B TERM
+The
+.B TERM
+variable is used to relate a tty device with its device capability
+description (see
+.BR terminfo (5)).
+.B TERM
+is set at login time, either by the default terminal type specified in
+.I /etc/ttys
+or as set during the login process by the user in their
+.B login
+file (see
+.BR setenv (3)).
+.SH HISTORY
+The
+.B ul
+command appeared in 3.0BSD.
+.SH BUGS
+.B nroff
+usually outputs a series of backspaces and underlines intermixed with the
+text to indicate underlining. No attempt is made to optimize the backward
+motion.
+.SH SEE ALSO
+.BR colcrt (1),
+.BR login (1),
+.BR man (1),
+.BR nroff (1),
+.BR setenv (3),
+.BR terminfo (5)
+.SH AVAILABILITY
+The ul command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/text-utils/ul.c b/text-utils/ul.c
new file mode 100644
index 0000000..39b69a5
--- /dev/null
+++ b/text-utils/ul.c
@@ -0,0 +1,653 @@
+/*
+ * Copyright (c) 1980, 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+/*
+ * modified by Kars de Jong <jongk@cs.utwente.nl>
+ * to use terminfo instead of termcap.
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * added Native Language Support
+ * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
+ * modified to work correctly in multi-byte locales
+ */
+
+#include <stdio.h>
+#include <unistd.h> /* for getopt(), isatty() */
+#include <string.h> /* for memset(), strcpy() */
+#include <stdlib.h> /* for getenv() */
+#include <limits.h> /* for INT_MAX */
+#include <signal.h> /* for signal() */
+#include <errno.h>
+#include <getopt.h>
+
+#if defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+#elif defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+#elif defined(HAVE_TERM_H)
+# include <term.h>
+#endif
+
+#include "nls.h"
+#include "xalloc.h"
+#include "widechar.h"
+#include "c.h"
+#include "closestream.h"
+
+#ifdef HAVE_WIDECHAR
+/* Output an ASCII character as a wide character */
+static int put1wc(int c)
+{
+ if (putwchar(c) == WEOF)
+ return EOF;
+
+ return c;
+}
+#define putwp(s) tputs(s, STDOUT_FILENO, put1wc)
+#else
+#define putwp(s) putp(s)
+#endif
+
+static int handle_escape(FILE * f);
+static void filter(FILE *f);
+static void flushln(void);
+static void overstrike(void);
+static void iattr(void);
+static void initbuf(void);
+static void fwd(void);
+static void reverse(void);
+static void initinfo(void);
+static void outc(wint_t c, int width);
+static void xsetmode(int newmode);
+static void setcol(int newcol);
+static void needcol(int col);
+static void sig_handler(int signo);
+static void print_out(char *line);
+
+#define IESC '\033'
+#define SO '\016'
+#define SI '\017'
+#define HFWD '9'
+#define HREV '8'
+#define FREV '7'
+
+#define NORMAL 000
+#define ALTSET 001 /* Reverse */
+#define SUPERSC 002 /* Dim */
+#define SUBSC 004 /* Dim | Ul */
+#define UNDERL 010 /* Ul */
+#define BOLD 020 /* Bold */
+
+static int must_use_uc, must_overstrike;
+static char *CURS_UP,
+ *CURS_RIGHT,
+ *CURS_LEFT,
+ *ENTER_STANDOUT,
+ *EXIT_STANDOUT,
+ *ENTER_UNDERLINE,
+ *EXIT_UNDERLINE,
+ *ENTER_DIM,
+ *ENTER_BOLD,
+ *ENTER_REVERSE,
+ *UNDER_CHAR,
+ *EXIT_ATTRIBUTES;
+
+struct CHAR {
+ char c_mode;
+ wchar_t c_char;
+ int c_width;
+};
+
+static struct CHAR *obuf;
+static int obuflen;
+static int col, maxcol;
+static int mode;
+static int halfpos;
+static int upln;
+static int iflag;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<file> ...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Do underlining.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -t, -T, --terminal TERMINAL override the TERM environment variable\n"), out);
+ fputs(_(" -i, --indicated underlining is indicated via a separate line\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(30));
+
+ printf(USAGE_MAN_TAIL("ul(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c, ret, tflag = 0;
+ char *termtype;
+ FILE *f;
+
+ static const struct option longopts[] = {
+ { "terminal", required_argument, NULL, 't' },
+ { "indicated", no_argument, NULL, 'i' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ termtype = getenv("TERM");
+
+ while ((c = getopt_long(argc, argv, "it:T:Vh", longopts, NULL)) != -1)
+ switch (c) {
+
+ case 't':
+ case 'T':
+ /* for nroff compatibility */
+ termtype = optarg;
+ tflag = 1;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ setupterm(termtype, STDOUT_FILENO, &ret);
+ switch (ret) {
+
+ case 1:
+ break;
+
+ default:
+ warnx(_("trouble reading terminfo"));
+ /* fallthrough */
+
+ case 0:
+ if (tflag)
+ warnx(_("terminal `%s' is not known, defaulting to `dumb'"),
+ termtype);
+ setupterm("dumb", STDOUT_FILENO, (int *)0);
+ break;
+ }
+ initinfo();
+ if ((tigetflag("os") && ENTER_BOLD==NULL ) ||
+ (tigetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
+ must_overstrike = 1;
+ initbuf();
+ if (optind == argc)
+ filter(stdin);
+ else
+ for (; optind < argc; optind++) {
+ f = fopen(argv[optind],"r");
+ if (!f)
+ err(EXIT_FAILURE, _("cannot open %s"),
+ argv[optind]);
+ filter(f);
+ fclose(f);
+ }
+ free(obuf);
+ return EXIT_SUCCESS;
+}
+
+static int handle_escape(FILE * f)
+{
+ wint_t c;
+
+ switch (c = getwc(f)) {
+ case HREV:
+ if (halfpos == 0) {
+ mode |= SUPERSC;
+ halfpos--;
+ } else if (halfpos > 0) {
+ mode &= ~SUBSC;
+ halfpos--;
+ } else {
+ halfpos = 0;
+ reverse();
+ }
+ return 0;
+ case HFWD:
+ if (halfpos == 0) {
+ mode |= SUBSC;
+ halfpos++;
+ } else if (halfpos < 0) {
+ mode &= ~SUPERSC;
+ halfpos++;
+ } else {
+ halfpos = 0;
+ fwd();
+ }
+ return 0;
+ case FREV:
+ reverse();
+ return 0;
+ default:
+ /* unknown escape */
+ ungetwc(c, f);
+ return 1;
+ }
+}
+
+static void filter(FILE *f)
+{
+ wint_t c;
+ int i, w;
+
+ while ((c = getwc(f)) != WEOF) {
+ switch (c) {
+ case '\b':
+ setcol(col - 1);
+ continue;
+ case '\t':
+ setcol((col + 8) & ~07);
+ continue;
+ case '\r':
+ setcol(0);
+ continue;
+ case SO:
+ mode |= ALTSET;
+ continue;
+ case SI:
+ mode &= ~ALTSET;
+ continue;
+ case IESC:
+ if (handle_escape(f)) {
+ c = getwc(f);
+ errx(EXIT_FAILURE,
+ _("unknown escape sequence in input: %o, %o"), IESC, c);
+ }
+ continue;
+ case '_':
+ if (obuf[col].c_char || obuf[col].c_width < 0) {
+ while (col > 0 && obuf[col].c_width < 0)
+ col--;
+ w = obuf[col].c_width;
+ for (i = 0; i < w; i++)
+ obuf[col++].c_mode |= UNDERL | mode;
+ setcol(col);
+ continue;
+ }
+ obuf[col].c_char = '_';
+ obuf[col].c_width = 1;
+ /* fallthrough */
+ case ' ':
+ setcol(col + 1);
+ continue;
+ case '\n':
+ flushln();
+ continue;
+ case '\f':
+ flushln();
+ putwchar('\f');
+ continue;
+ default:
+ if (!iswprint(c))
+ /* non printable */
+ continue;
+ w = wcwidth(c);
+ needcol(col + w);
+ if (obuf[col].c_char == '\0') {
+ obuf[col].c_char = c;
+ for (i = 0; i < w; i++)
+ obuf[col + i].c_mode = mode;
+ obuf[col].c_width = w;
+ for (i = 1; i < w; i++)
+ obuf[col + i].c_width = -1;
+ } else if (obuf[col].c_char == '_') {
+ obuf[col].c_char = c;
+ for (i = 0; i < w; i++)
+ obuf[col + i].c_mode |= UNDERL | mode;
+ obuf[col].c_width = w;
+ for (i = 1; i < w; i++)
+ obuf[col + i].c_width = -1;
+ } else if ((wint_t) obuf[col].c_char == c) {
+ for (i = 0; i < w; i++)
+ obuf[col + i].c_mode |= BOLD | mode;
+ } else {
+ w = obuf[col].c_width;
+ for (i = 0; i < w; i++)
+ obuf[col + i].c_mode = mode;
+ }
+ setcol(col + w);
+ continue;
+ }
+ }
+ if (maxcol)
+ flushln();
+}
+
+static void flushln(void)
+{
+ int lastmode;
+ int i;
+ int hadmodes = 0;
+
+ lastmode = NORMAL;
+ for (i = 0; i < maxcol; i++) {
+ if (obuf[i].c_mode != lastmode) {
+ hadmodes++;
+ xsetmode(obuf[i].c_mode);
+ lastmode = obuf[i].c_mode;
+ }
+ if (obuf[i].c_char == '\0') {
+ if (upln) {
+ print_out(CURS_RIGHT);
+ } else
+ outc(' ', 1);
+ } else
+ outc(obuf[i].c_char, obuf[i].c_width);
+ if (obuf[i].c_width > 1)
+ i += obuf[i].c_width - 1;
+ }
+ if (lastmode != NORMAL) {
+ xsetmode(0);
+ }
+ if (must_overstrike && hadmodes)
+ overstrike();
+ putwchar('\n');
+ if (iflag && hadmodes)
+ iattr();
+ fflush(stdout);
+ if (upln)
+ upln--;
+ initbuf();
+}
+
+/*
+ * For terminals that can overstrike, overstrike underlines and bolds.
+ * We don't do anything with halfline ups and downs, or Greek.
+ */
+static void overstrike(void)
+{
+ register int i;
+ register wchar_t *lbuf = xcalloc(maxcol + 1, sizeof(wchar_t));
+ register wchar_t *cp = lbuf;
+ int hadbold=0;
+
+ /* Set up overstrike buffer */
+ for (i = 0; i < maxcol; i++)
+ switch (obuf[i].c_mode) {
+ case NORMAL:
+ default:
+ *cp++ = ' ';
+ break;
+ case UNDERL:
+ *cp++ = '_';
+ break;
+ case BOLD:
+ *cp++ = obuf[i].c_char;
+ if (obuf[i].c_width > 1)
+ i += obuf[i].c_width - 1;
+ hadbold=1;
+ break;
+ }
+ putwchar('\r');
+ for (*cp = ' '; *cp == ' '; cp--)
+ *cp = 0;
+ fputws(lbuf, stdout);
+ if (hadbold) {
+ putwchar('\r');
+ for (cp = lbuf; *cp; cp++)
+ putwchar(*cp == '_' ? ' ' : *cp);
+ putwchar('\r');
+ for (cp = lbuf; *cp; cp++)
+ putwchar(*cp == '_' ? ' ' : *cp);
+ }
+ free(lbuf);
+}
+
+static void iattr(void)
+{
+ register int i;
+ register wchar_t *lbuf = xcalloc(maxcol + 1, sizeof(wchar_t));
+ register wchar_t *cp = lbuf;
+
+ for (i = 0; i < maxcol; i++)
+ switch (obuf[i].c_mode) {
+ case NORMAL: *cp++ = ' '; break;
+ case ALTSET: *cp++ = 'g'; break;
+ case SUPERSC: *cp++ = '^'; break;
+ case SUBSC: *cp++ = 'v'; break;
+ case UNDERL: *cp++ = '_'; break;
+ case BOLD: *cp++ = '!'; break;
+ default: *cp++ = 'X'; break;
+ }
+ for (*cp = ' '; *cp == ' '; cp--)
+ *cp = 0;
+ fputws(lbuf, stdout);
+ putwchar('\n');
+ free(lbuf);
+}
+
+static void initbuf(void)
+{
+ if (obuf == NULL) {
+ /* First time. */
+ obuflen = BUFSIZ;
+ obuf = xcalloc(obuflen, sizeof(struct CHAR));
+ } else
+ /* assumes NORMAL == 0 */
+ memset(obuf, 0, sizeof(struct CHAR) * maxcol);
+
+ setcol(0);
+ maxcol = 0;
+ mode &= ALTSET;
+}
+
+static void fwd(void)
+{
+ int oldcol, oldmax;
+
+ oldcol = col;
+ oldmax = maxcol;
+ flushln();
+ setcol(oldcol);
+ maxcol = oldmax;
+}
+
+static void reverse(void)
+{
+ upln++;
+ fwd();
+ print_out(CURS_UP);
+ print_out(CURS_UP);
+ upln++;
+}
+
+static void initinfo(void)
+{
+ CURS_UP = tigetstr("cuu1");
+ CURS_RIGHT = tigetstr("cuf1");
+ CURS_LEFT = tigetstr("cub1");
+ if (CURS_LEFT == NULL)
+ CURS_LEFT = "\b";
+
+ ENTER_STANDOUT = tigetstr("smso");
+ EXIT_STANDOUT = tigetstr("rmso");
+ ENTER_UNDERLINE = tigetstr("smul");
+ EXIT_UNDERLINE = tigetstr("rmul");
+ ENTER_DIM = tigetstr("dim");
+ ENTER_BOLD = tigetstr("bold");
+ ENTER_REVERSE = tigetstr("rev");
+ EXIT_ATTRIBUTES = tigetstr("sgr0");
+
+ if (!ENTER_BOLD && ENTER_REVERSE)
+ ENTER_BOLD = ENTER_REVERSE;
+ if (!ENTER_BOLD && ENTER_STANDOUT)
+ ENTER_BOLD = ENTER_STANDOUT;
+ if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
+ ENTER_UNDERLINE = ENTER_STANDOUT;
+ EXIT_UNDERLINE = EXIT_STANDOUT;
+ }
+ if (!ENTER_DIM && ENTER_STANDOUT)
+ ENTER_DIM = ENTER_STANDOUT;
+ if (!ENTER_REVERSE && ENTER_STANDOUT)
+ ENTER_REVERSE = ENTER_STANDOUT;
+ if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
+ EXIT_ATTRIBUTES = EXIT_STANDOUT;
+
+ /*
+ * Note that we use REVERSE for the alternate character set,
+ * not the as/ae capabilities. This is because we are modeling
+ * the model 37 teletype (since that's what nroff outputs) and
+ * the typical as/ae is more of a graphics set, not the greek
+ * letters the 37 has.
+ */
+
+ UNDER_CHAR = tigetstr("uc");
+ must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
+}
+
+static int curmode = 0;
+
+static void outc(wint_t c, int width) {
+ int i;
+
+ putwchar(c);
+ if (must_use_uc && (curmode&UNDERL)) {
+ for (i = 0; i < width; i++)
+ print_out(CURS_LEFT);
+ for (i = 0; i < width; i++)
+ print_out(UNDER_CHAR);
+ }
+}
+
+static void xsetmode(int newmode)
+{
+ if (!iflag) {
+ if (curmode != NORMAL && newmode != NORMAL)
+ xsetmode(NORMAL);
+ switch (newmode) {
+ case NORMAL:
+ switch (curmode) {
+ case NORMAL:
+ break;
+ case UNDERL:
+ print_out(EXIT_UNDERLINE);
+ break;
+ default:
+ /* This includes standout */
+ print_out(EXIT_ATTRIBUTES);
+ break;
+ }
+ break;
+ case ALTSET:
+ print_out(ENTER_REVERSE);
+ break;
+ case SUPERSC:
+ /*
+ * This only works on a few terminals.
+ * It should be fixed.
+ */
+ print_out(ENTER_UNDERLINE);
+ print_out(ENTER_DIM);
+ break;
+ case SUBSC:
+ print_out(ENTER_DIM);
+ break;
+ case UNDERL:
+ print_out(ENTER_UNDERLINE);
+ break;
+ case BOLD:
+ print_out(ENTER_BOLD);
+ break;
+ default:
+ /*
+ * We should have some provision here for multiple modes
+ * on at once. This will have to come later.
+ */
+ print_out(ENTER_STANDOUT);
+ break;
+ }
+ }
+ curmode = newmode;
+}
+
+static void setcol(int newcol) {
+ col = newcol;
+
+ if (col < 0)
+ col = 0;
+ else if (col > maxcol)
+ needcol(col);
+}
+
+static void needcol(int acol) {
+ maxcol = acol;
+
+ /* If col >= obuflen, expand obuf until obuflen > col. */
+ while (acol >= obuflen) {
+ /* Paranoid check for obuflen == INT_MAX. */
+ if (obuflen == INT_MAX)
+ errx(EXIT_FAILURE, _("Input line too long."));
+
+ /* Similar paranoia: double only up to INT_MAX. */
+ if (obuflen < (INT_MAX / 2))
+ obuflen *= 2;
+ else
+ obuflen = INT_MAX;
+
+ /* Now we can try to expand obuf. */
+ obuf = xrealloc(obuf, sizeof(struct CHAR) * obuflen);
+ }
+}
+
+static void sig_handler(int signo __attribute__ ((__unused__)))
+{
+ _exit(EXIT_SUCCESS);
+}
+
+static void print_out(char *line)
+{
+ if (line == NULL)
+ return;
+
+ putwp(line);
+}