summaryrefslogtreecommitdiffstats
path: root/text-utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:42:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:42:50 +0000
commit8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch)
treea9b2e7baeca1be40eb734371e3c8b11b02294497 /text-utils
parentInitial commit. (diff)
downloadutil-linux-upstream.tar.xz
util-linux-upstream.zip
Adding upstream version 2.33.1.upstream/2.33.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.c600
-rw-r--r--text-utils/colcrt.1105
-rw-r--r--text-utils/colcrt.c294
-rw-r--r--text-utils/colrm.175
-rw-r--r--text-utils/colrm.c194
-rw-r--r--text-utils/column.1180
-rw-r--r--text-utils/column.c855
-rw-r--r--text-utils/hexdump-conv.c111
-rw-r--r--text-utils/hexdump-display.c455
-rw-r--r--text-utils/hexdump-parse.c645
-rw-r--r--text-utils/hexdump.1379
-rw-r--r--text-utils/hexdump.c251
-rw-r--r--text-utils/hexdump.h108
-rw-r--r--text-utils/line.117
-rw-r--r--text-utils/line.c84
-rw-r--r--text-utils/more.1246
-rw-r--r--text-utils/more.c2100
-rw-r--r--text-utils/pg.1238
-rw-r--r--text-utils/pg.c1691
-rw-r--r--text-utils/rev.159
-rw-r--r--text-utils/rev.c183
-rw-r--r--text-utils/ul.1113
-rw-r--r--text-utils/ul.c653
25 files changed, 9892 insertions, 0 deletions
diff --git a/text-utils/Makemodule.am b/text-utils/Makemodule.am
new file mode 100644
index 0000000..7478eb4
--- /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)
+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..f0b50b6
--- /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 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 SEE ALSO
+.BR expand (1),
+.BR nroff (1),
+.BR tbl (1)
+.SH STANDARDS
+The
+.B col
+utility conforms to the Single UNIX Specification, Version 2. The
+.B \-l
+option is an extension to the standard.
+.SH HISTORY
+A
+.B col
+command appeared in Version 6 AT&T UNIX.
+.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..3d9e15d
--- /dev/null
+++ b/text-utils/col.c
@@ -0,0 +1,600 @@
+/*-
+ * 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
+ short 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);
+ atexit(close_stdout);
+
+ 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':
+ printf(UTIL_LINUX_VERSION);
+ return 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(NULL);
+ 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)
+ 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;
+ static int count_size, *count, i, save, sorted_size, 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; --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++) {
+ 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..dbfffb4
--- /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 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 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 HISTORY
+The
+.B colcrt
+command appeared in 3.0BSD.
+.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..6113880
--- /dev/null
+++ b/text-utils/colcrt.c
@@ -0,0 +1,294 @@
+/*
+ * 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;
+ 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 ((c = getwc(ctl->f)) != L'\n') {
+ long new_pos = ftell(ctl->f);
+ if (old_pos == new_pos)
+ fseek(ctl->f, 1, SEEK_CUR);
+ else
+ old_pos = new_pos;
+ if (errno == 0 && c == WEOF)
+ return;
+ else
+ errno = 0;
+ }
+ 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);
+ atexit(close_stdout);
+
+ /* 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':
+ printf(UTIL_LINUX_VERSION);
+ return 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..2c50f9a
--- /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 SEE ALSO
+.BR awk (1),
+.BR column (1),
+.BR expand (1),
+.BR paste (1)
+.SH HISTORY
+The
+.B colrm
+command appeared in 3.0BSD.
+.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..8bb0ee9
--- /dev/null
+++ b/text-utils/colrm.c
@@ -0,0 +1,194 @@
+/*
+ * 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);
+ atexit(close_stdout);
+
+ while ((opt =
+ getopt_long(argc, argv, "bfhl:pxVH", longopts,
+ NULL)) != -1)
+ switch (opt) {
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return 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..9ce5879
--- /dev/null
+++ b/text-utils/column.1
@@ -0,0 +1,180 @@
+.\" 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 "January 2017" "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.
+.PP
+.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.
+.PP
+.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 to use logical column names on command line, but keep the header hidden when print 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 is allowed to truncate text 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 '-' maybe 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
+another 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.
+.PP
+.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 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
+
+.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 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
+.SH "SEE ALSO"
+.BR colrm (1),
+.BR ls (1),
+.BR paste (1),
+.BR sort (1)
+.SH HISTORY
+The column command appeared in 4.3BSD-Reno.
+.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..8961bd3
--- /dev/null
+++ b/text-utils/column.c
@@ -0,0 +1,855 @@
+/*
+ * 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(wchar_t *p, const wchar_t *separator, int greedy, wchar_t **state)
+{
+ wchar_t *result = NULL;
+
+ if (greedy)
+#ifdef HAVE_WIDECHAR
+ return wcstok(p, separator, state);
+#else
+ return strtok_r(p, separator, state);
+#endif
+ if (!p) {
+ if (!*state || !**state)
+ return NULL;
+ p = *state;
+ }
+ result = p;
+#ifdef HAVE_WIDECHAR
+ p = wcspbrk(result, separator);
+#else
+ p = strpbrk(result, 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(wcs, ctl->input_separator, ctl->greedy, &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;
+ }
+ } 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);
+ atexit(close_stdout);
+
+ 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 'h':
+ usage();
+ 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 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ case 'W':
+ ctl.tab_colwrap = optarg;
+ break;
+ case 'x':
+ ctl.mode = COLUMN_MODE_FILLROWS;
+ break;
+ 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..0c4f15f
--- /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", "dcl", "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..3e01763
--- /dev/null
+++ b/text-utils/hexdump-display.c
@@ -0,0 +1,455 @@
+/*
+ * 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;
+ continue;
+ }
+
+ /* 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)) {
+ 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..2b2735b
--- /dev/null
+++ b/text-utils/hexdump-parse.c
@@ -0,0 +1,645 @@
+/*
+ * 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 */
+ while (++p1 && strchr(spec, *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);
+ 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..5651a6f
--- /dev/null
+++ b/text-utils/hexdump.1
@@ -0,0 +1,379 @@
+.\" 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.
+.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
+.PP
+.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
+.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 EXAMPLES
+Display the input in perusal format:
+.nf
+ "%06.6_ao " 12/1 "%3_u "
+ "\et\et" "%_p "
+ "\en"
+.nf
+.PP
+Implement the \-x option:
+.nf
+ "%07.7_Ax\en"
+ "%07.7_ax " 8/2 "%04x " "\en"
+.nf
+.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"
+.nf
+.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 STANDARDS
+The
+.B hexdump
+utility is expected to be IEEE Std 1003.2 ("POSIX.2") compatible.
+.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..94c53b8
--- /dev/null
+++ b/text-utils/hexdump.c
@@ -0,0 +1,251 @@
+/*
+ * 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}
+ };
+
+ 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':
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ break;
+ 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));
+ 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);
+ atexit(close_stdout);
+
+ 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..4a8d109
--- /dev/null
+++ b/text-utils/hexdump.h
@@ -0,0 +1,108 @@
+/*
+ * 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
+ */
+#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 *);
diff --git a/text-utils/line.1 b/text-utils/line.1
new file mode 100644
index 0000000..08236e2
--- /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 (1)
+.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..63c0012
--- /dev/null
+++ b/text-utils/line.c
@@ -0,0 +1,84 @@
+/*
+ * 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);
+ atexit(close_stdout);
+
+ while ((opt = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (opt) {
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return 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..c4420e0
--- /dev/null
+++ b/text-utils/more.1
@@ -0,0 +1,246 @@
+.\" 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" "February 2014" "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
+.B \-d
+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
+.B \-l
+Do not pause after any line containing a
+.B \&^L
+(form feed).
+.TP
+.B \-f
+Count logical lines, rather than screen lines (i.e., long lines are not folded).
+.TP
+.B \-p
+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
+.B \-c
+Do not scroll. Instead, paint each screen from the top, clearing the
+remainder of each line as it is displayed.
+.TP
+.B \-s
+Squeeze multiple blank lines into one.
+.TP
+.B \-u
+Suppress underlining.
+.TP
+.BI \- number
+The screen size to use, in
+.I number
+of lines.
+.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.
+.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 SEE ALSO
+.BR less (1),
+.BR vi (1)
+.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 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 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..8d394bc
--- /dev/null
+++ b/text-utils/more.c
@@ -0,0 +1,2100 @@
+/*
+ * 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 MERCHANTIBILITY 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 <setjmp.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/wait.h>
+#include <regex.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 "strutils.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "widechar.h"
+#include "closestream.h"
+
+#ifdef TEST_PROGRAM
+# define NON_INTERACTIVE_MORE 1
+#endif
+
+#define VI "vi" /* found on the user's path */
+
+#define BS "\b"
+#define BSB "\b \b"
+#define CARAT "^"
+#define RINGBELL '\007'
+
+#define MIN_LINE_SZ 256 /* minimal line_buf buffer size */
+#define ctrl(letter) (letter & 077)
+#define RUBOUT '\177'
+#define ESC '\033'
+#define QUIT '\034'
+#define SCROLL_LEN 11
+#define LINES_PER_PAGE 24
+#define NUM_COLUMNS 80
+#define TERMINAL_BUF 4096
+#define INIT_BUF 80
+#define SHELL_LINE 1000
+#define COMMAND_BUF 200
+#define REGERR_BUF NUM_COLUMNS
+
+#define TERM_AUTO_RIGHT_MARGIN "am"
+#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_ENTER_UNDERLINE "smul"
+#define TERM_EXIT_STANDARD_MODE "rmso"
+#define TERM_EXIT_UNDERLINE "rmul"
+#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_PAD_CHAR "pad"
+#define TERM_STANDARD_MODE "smso"
+#define TERM_STD_MODE_GLITCH "xmc"
+#define TERM_UNDERLINE_CHAR "uc"
+#define TERM_UNDERLINE "ul"
+
+struct more_control {
+ struct termios output_tty; /* output terminal */
+ struct termios original_tty; /* original terminal settings */
+ long file_position; /* file position */
+ long 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 */
+ 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 previous_shell; /* does previous shell command exist */
+ sigjmp_buf destination; /* siglongjmp() destination */
+ 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 *enter_underline; /* enter underline mode */
+ char *exit_underline; /* exit underline mode */
+ char *underline_ch; /* underline character */
+ char *backspace_ch; /* backspace character */
+ char *go_home; /* go to home */
+ char *cursor_addr; /* cursor movement */
+ char home_position[40]; /* contains cursor movement to home */
+ char *clear_rest; /* clear rest of screen */
+ int num_columns; /* number of columns */
+ char *previous_search; /* previous search() buf[] item */
+ struct {
+ long row_num; /* row number */
+ long line_num; /* line number */
+ } context,
+ screen_start;
+ int last_key_command; /* previous more key command */
+ int last_key_arg; /* previous key command argument */
+ int last_colon_command; /* is a colon-prefixed key command */
+ char shell_line[SHELL_LINE];
+ 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 */
+ dumb_tty:1, /* is terminal type known */
+ eat_newline:1, /* is newline ignored after 80 cols */
+ enable_underlining:1, /* underline as best we can */
+ 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) */
+ 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 */
+ report_errors:1, /* is an error reported */
+ run_previous_command:1, /* run previous key command */
+ squeeze_spaces:1, /* suppress white space */
+ starting_up:1, /* is startup completed */
+ stdout_glitch:1, /* terminal has standout mode glitch */
+ stop_after_formfeed:1, /* stop after form feeds */
+ suppress_bell:1, /* suppress bell */
+ underline_glitch:1, /* terminal has underline mode glitch */
+ underline_state:1, /* current UL state */
+ waiting_input:1, /* is waiting user input */
+ within_file:1, /* true if we are within a file, false if we are between files */
+ wrap_margin:1; /* set if automargins */
+};
+
+/* FIXME: global_ctl is used in signal handlers. */
+struct more_control *global_ctl;
+
+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(_("A file perusal filter for CRT viewing.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -d display help instead of ringing bell\n"), out);
+ fputs(_(" -f count logical rather than screen lines\n"), out);
+ fputs(_(" -l suppress pause after form feed\n"), out);
+ fputs(_(" -c do not scroll, display text and clean line ends\n"), out);
+ fputs(_(" -p do not scroll, clean screen and display text\n"), out);
+ fputs(_(" -s squeeze multiple blank lines into one\n"), out);
+ fputs(_(" -u suppress underlining\n"), out);
+ fputs(_(" -<number> the number of lines per screenful\n"), out);
+ fputs(_(" +<number> display file beginning from line number\n"), out);
+ fputs(_(" +/<string> display file beginning from search string match\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf( " --help %s\n", USAGE_OPTSTR_HELP);
+ printf( " -V, --version %s\n", USAGE_OPTSTR_VERSION);
+ printf(USAGE_MAN_TAIL("more(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+
+static void arg_parser(struct more_control *ctl, char *s)
+{
+ int seen_num = 0;
+
+ while (*s != '\0') {
+ switch (*s) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (!seen_num) {
+ ctl->lines_per_screen = 0;
+ seen_num = 1;
+ }
+ ctl->lines_per_screen = ctl->lines_per_screen * 10 + *s - '0';
+ break;
+ 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':
+ ctl->enable_underlining = 0;
+ break;
+ case '-':
+ case ' ':
+ case '\t':
+ break;
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ break;
+ default:
+ warnx(_("unknown option -%s"), s);
+ errtryhelp(EXIT_FAILURE);
+ break;
+ }
+ s++;
+ }
+}
+
+static void more_fseek(struct more_control *ctl, FILE *stream, long pos)
+{
+ ctl->file_position = pos;
+ fseek(stream, pos, 0);
+}
+
+static int more_getc(struct more_control *ctl, FILE *stream)
+{
+ ctl->file_position++;
+ return getc(stream);
+}
+
+static int more_ungetc(struct more_control *ctl, int c, FILE *stream)
+{
+ ctl->file_position--;
+ return ungetc(c, stream);
+}
+
+/* magic --
+ * check for file magic numbers. This code would best be shared
+ * with the file(1) program or, perhaps, more should not try to be
+ * so smart. */
+static int check_magic(FILE *f, char *fs)
+{
+ signed char twobytes[2];
+
+ /* don't try to look ahead if the input is unseekable */
+ if (fseek(f, 0L, SEEK_SET))
+ return 0;
+
+ if (fread(twobytes, 2, 1, f) == 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(f, 0L, SEEK_SET); /* rewind() not necessary */
+ 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 FILE *checkf(struct more_control *ctl, char *fs, int *clearfirst)
+{
+ struct stat st;
+ FILE *f;
+ int c;
+
+ if (stat(fs, &st) == -1) {
+ fflush(stdout);
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ warn(_("stat of %s failed"), fs);
+ return NULL;
+ }
+ if ((st.st_mode & S_IFMT) == S_IFDIR) {
+ printf(_("\n*** %s: directory ***\n\n"), fs);
+ return NULL;
+ }
+ ctl->current_line = 0;
+ ctl->file_position = 0;
+ if ((f = fopen(fs, "r")) == NULL) {
+ fflush(stdout);
+ warn(_("cannot open %s"), fs);
+ return NULL;
+ }
+ if (check_magic(f, fs)) {
+ fclose(f);
+ return NULL;
+ }
+ fcntl(fileno(f), F_SETFD, FD_CLOEXEC);
+ c = more_getc(ctl, f);
+ *clearfirst = (c == '\f');
+ more_ungetc(ctl, c, f);
+ if ((ctl->file_size = st.st_size) == 0)
+ ctl->file_size = LONG_MAX;
+ return f;
+}
+
+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, FILE *f, 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(). */
+ long file_position_bak = ctl->file_position;
+
+ memset(&state, '\0', sizeof(mbstate_t));
+#endif
+
+ prepare_line_buffer(ctl);
+
+ p = ctl->line_buf;
+ column = 0;
+ c = more_getc(ctl, f);
+ if (column_wrap && c == '\n') {
+ ctl->current_line++;
+ c = more_getc(ctl, f);
+ }
+ 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, f, 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, f, 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, f);
+ 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, f);
+ if (next == '\n') {
+ p--;
+ ctl->current_line++;
+ break;
+ }
+ more_ungetc(ctl, c, f);
+ 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, f);
+ }
+ 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_prompt(struct more_control *ctl, int col)
+{
+
+ if (ctl->prompt_len == 0)
+ return;
+ 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, "");
+ }
+ ctl->prompt_len = 0;
+}
+
+#ifdef HAVE_WIDECHAR
+static UL_ASAN_BLACKLIST size_t xmbrtowc(wchar_t *wc, const char *s, size_t n,
+ mbstate_t *mbstate)
+{
+ const size_t mblength = mbrtowc(wc, s, n, mbstate);
+ if (mblength == (size_t)-2 || mblength == (size_t)-1)
+ return 1;
+ return mblength;
+}
+#endif
+
+static int would_underline(char *s, int n)
+{
+ if (n < 2)
+ return 0;
+ if ((s[0] == '_' && s[1] == '\b') || (s[1] == '\b' && s[2] == '_'))
+ return 1;
+ return 0;
+}
+
+/* Print a buffer of n characters */
+static void print_buf(struct more_control *ctl, char *s, int n)
+{
+ char c; /* next output character */
+ int state; /* next output char's UL state */
+
+ while (--n >= 0)
+ if (!ctl->enable_underlining)
+ putchar(*s++);
+ else {
+ if (*s == ' ' && ctl->underline_state == 0 && ctl->underline_glitch
+ && would_underline(s + 1, n - 1)) {
+ s++;
+ continue;
+ }
+ if ((state = would_underline(s, n)) != 0) {
+ c = (*s == '_') ? s[2] : *s;
+ n -= 2;
+ s += 3;
+ } else
+ c = *s++;
+ if (state != ctl->underline_state) {
+ if (c == ' ' && state == 0 && ctl->underline_glitch
+ && would_underline(s, n - 1))
+ state = 1;
+ else
+ putp(state ? ctl->enter_underline : ctl->exit_underline);
+ }
+ if (c != ' ' || ctl->underline_state == 0 || state != 0
+ || ctl->underline_glitch == 0)
+#ifdef HAVE_WIDECHAR
+ {
+ wchar_t wc;
+ size_t mblength;
+ mbstate_t mbstate;
+ memset(&mbstate, '\0', sizeof(mbstate_t));
+ s--;
+ n++;
+ mblength = xmbrtowc(&wc, s, n, &mbstate);
+ while (mblength--)
+ putchar(*s++);
+ n += mblength;
+ }
+#else
+ putchar(c);
+#endif /* HAVE_WIDECHAR */
+ if (state && *ctl->underline_ch) {
+ fputs(ctl->backspace_ch, stdout);
+ putp(ctl->underline_ch);
+ }
+ ctl->underline_state = state;
+ }
+}
+
+/* Erase the current line entirely */
+static void kill_line(struct more_control *ctl)
+{
+ erase_prompt(ctl, 0);
+ if (!ctl->erase_line || ctl->dumb_tty)
+ putchar('\r');
+}
+
+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)
+ kill_line(ctl);
+ if (!ctl->hard_tty) {
+ ctl->prompt_len = 0;
+ if (ctl->enter_std && ctl->exit_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->enter_std && ctl->exit_std)
+ putp(ctl->exit_std);
+ if (ctl->clear_line_ends)
+ putp(ctl->clear_rest);
+ fflush(stdout);
+ } else
+ fputc(RINGBELL, stderr);
+ ctl->waiting_input++;
+}
+
+static void reset_tty(void)
+{
+ if (global_ctl->no_tty_out)
+ return;
+ if (global_ctl->underline_state) {
+ putp(global_ctl->exit_underline);
+ fflush(stdout);
+ global_ctl->underline_state = 0;
+ }
+ global_ctl->output_tty.c_lflag |= ICANON | ECHO;
+ global_ctl->output_tty.c_cc[VMIN] = global_ctl->original_tty.c_cc[VMIN];
+ global_ctl->output_tty.c_cc[VTIME] = global_ctl->original_tty.c_cc[VTIME];
+ tcsetattr(STDERR_FILENO, TCSANOW, &global_ctl->original_tty);
+}
+
+/* Clean up terminal state and exit. Also come here if interrupt signal received */
+static void __attribute__((__noreturn__)) more_exit(int dummy __attribute__((__unused__)))
+{
+ /* May be executed as a signal handler as well as by main process.
+ *
+ * The _exit() may wait for pending I/O for really long time, be sure
+ * that signal handler is not executed in this time to avoid double
+ * de-initialization (free() calls, etc.).
+ */
+ signal(SIGINT, SIG_IGN);
+
+ reset_tty();
+ if (global_ctl->clear_line_ends) {
+ putchar('\r');
+ putp(global_ctl->erase_line);
+ fflush(stdout);
+ } else if (!global_ctl->clear_line_ends && (global_ctl->prompt_len > 0)) {
+ kill_line(global_ctl);
+ fflush(stdout);
+ } else
+ fputc('\n', stderr);
+ free(global_ctl->previous_search);
+ free(global_ctl->line_buf);
+ _exit(EXIT_SUCCESS);
+}
+
+static int read_user_input(struct more_control *ctl)
+{
+ unsigned char c;
+
+ errno = 0;
+ if (read(STDERR_FILENO, &c, 1) <= 0) {
+ if (errno != EINTR)
+ more_exit(0);
+ else
+ c = ctl->output_tty.c_cc[VKILL];
+ }
+ return c;
+}
+
+/* Read a decimal number from the terminal. Set cmd to the non-digit
+ * which terminates the number. */
+static int read_number(struct more_control *ctl, char *cmd)
+{
+ int i;
+ char ch;
+
+ i = 0;
+ ch = ctl->output_tty.c_cc[VKILL];
+ for (;;) {
+ ch = read_user_input(ctl);
+ if (isdigit(ch))
+ i = i * 10 + ch - '0';
+ else if ((cc_t) ch == ctl->output_tty.c_cc[VKILL])
+ i = 0;
+ else {
+ *cmd = ch;
+ break;
+ }
+ }
+ return i;
+}
+
+/* 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;
+ } else if (ctl->within_file)
+ ctl->argv_position++;
+ 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 == RUBOUT) {
+ c += (c == RUBOUT) ? -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
+ kill_line(ctl);
+ ctl->prompt_len += strlen(mess);
+ if (ctl->enter_std && ctl->exit_std) {
+ putp(ctl->enter_std);
+ fputs(mess, stdout);
+ putp(ctl->exit_std);
+ } else
+ fputs(mess, stdout);
+ fflush(stdout);
+ ctl->report_errors++;
+ siglongjmp(ctl->destination, 1);
+}
+
+static void erase_one_column(struct more_control *ctl)
+{
+ if (ctl->erase_previous_ok)
+ fputs(BSB, stderr);
+ else
+ fputs(BS, stderr);
+}
+
+static void ttyin(struct more_control *ctl, char buf[], int nmax, char pchar)
+{
+ char *sp;
+ int 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 (((cc_t) 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);
+
+ state = (mblength == (size_t)-2
+ || mblength ==
+ (size_t)-1) ? state_bak
+ : state;
+ mblength =
+ (mblength == (size_t)-2
+ || mblength == (size_t)-1
+ || mblength ==
+ 0) ? 1 : mblength;
+
+ 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 == RUBOUT) {
+ --ctl->prompt_len;
+ erase_one_column(ctl);
+ }
+ continue;
+ } else {
+ if (!ctl->erase_line)
+ ctl->prompt_len = maxlen;
+ siglongjmp(ctl->destination, 1);
+ }
+ } else if (((cc_t) 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_prompt(ctl, 1);
+ else if (ctl->erase_input_ok)
+ while (ctl->prompt_len-- > 1)
+ fputs(BSB, stderr);
+ ctl->prompt_len = 1;
+ }
+ sp = buf;
+ fflush(stdout);
+ continue;
+ }
+ if (slash && ((cc_t) c == ctl->output_tty.c_cc[VKILL]
+ || (cc_t) 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 == RUBOUT) {
+ c += (c == RUBOUT) ? -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"));
+}
+
+static int expand(struct more_control *ctl, char **outbuf, char *inbuf)
+{
+ char *inpstr;
+ char *outstr;
+ char c;
+ char *temp;
+ int changed = 0;
+ int tempsz, xtra, offset;
+
+ xtra = strlen(ctl->file_names[ctl->argv_position]) + strlen(ctl->shell_line) + 1;
+ tempsz = 200 + xtra;
+ temp = xmalloc(tempsz);
+ inpstr = inbuf;
+ outstr = temp;
+ while ((c = *inpstr++) != '\0') {
+ offset = outstr - temp;
+ if (tempsz - offset - 1 < xtra) {
+ tempsz += 200 + 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]);
+ changed++;
+ } else
+ *outstr++ = c;
+ break;
+ case '!':
+ if (!ctl->previous_shell)
+ more_error(ctl, _
+ ("No previous command to substitute for"));
+ strcpy(outstr, ctl->shell_line);
+ outstr += strlen(ctl->shell_line);
+ changed++;
+ break;
+ case '\\':
+ if (*inpstr == '%' || *inpstr == '!') {
+ *outstr++ = *inpstr++;
+ break;
+ }
+ /* fallthrough */
+ default:
+ *outstr++ = c;
+ }
+ }
+ *outstr++ = '\0';
+ *outbuf = temp;
+ return changed;
+}
+
+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(int dummy __attribute__((__unused__)))
+{
+ signal(SIGQUIT, SIG_IGN);
+ if (!global_ctl->waiting_input) {
+ putchar('\n');
+ if (!global_ctl->starting_up) {
+ signal(SIGQUIT, sigquit_handler);
+ siglongjmp(global_ctl->destination, 1);
+ } else
+ global_ctl->is_paused = 1;
+ } else if (!global_ctl->suppress_bell && global_ctl->no_quit_dialog) {
+ global_ctl->prompt_len += fprintf(stderr, _("[Use q or Q to quit]"));
+ global_ctl->no_quit_dialog = 0;
+ }
+ signal(SIGQUIT, sigquit_handler);
+}
+
+/* Come here when we get a suspend signal from the terminal */
+static void sigtstp_handler(int dummy __attribute__((__unused__)))
+{
+ sigset_t signals, oldmask;
+
+ /* ignore SIGTTOU so we don't get stopped if csh grabs the tty */
+ signal(SIGTTOU, SIG_IGN);
+ reset_tty();
+ fflush(stdout);
+ signal(SIGTTOU, SIG_DFL);
+ /* Send the TSTP signal to suspend our process group */
+ signal(SIGTSTP, SIG_DFL);
+
+ /* unblock SIGTSTP or we won't be able to suspend ourself */
+ sigemptyset(&signals);
+ sigaddset(&signals, SIGTSTP);
+ sigprocmask(SIG_UNBLOCK, &signals, &oldmask);
+
+ kill(0, SIGTSTP);
+ /* Pause for station break */
+
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ /* We're back */
+ signal(SIGTSTP, sigtstp_handler);
+ set_tty(global_ctl);
+ if (global_ctl->waiting_input)
+ siglongjmp(global_ctl->destination, 1);
+}
+
+static void execute(struct more_control *ctl, char *filename, char *cmd, ...)
+{
+ int id;
+ int n;
+ va_list argp;
+ char *arg;
+ char **args;
+ int argcount;
+
+ fflush(stdout);
+ reset_tty();
+ for (n = 10; (id = fork()) < 0 && n > 0; n--)
+ sleep(5);
+ if (id == 0) {
+ int errsv;
+ if (!isatty(STDIN_FILENO)) {
+ close(STDIN_FILENO);
+ open("/dev/tty", 0);
+ }
+
+ 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);
+
+ execvp(cmd, args);
+ errsv = errno;
+ fputs(_("exec failed\n"), stderr);
+ exit(errsv == ENOENT ? EX_EXEC_ENOENT : EX_EXEC_FAILED);
+ }
+ if (id > 0) {
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ if (ctl->catch_suspend)
+ signal(SIGTSTP, SIG_DFL);
+ while (wait(NULL) > 0)
+ /* nothing */ ;
+ signal(SIGINT, more_exit);
+ signal(SIGQUIT, sigquit_handler);
+ if (ctl->catch_suspend)
+ signal(SIGTSTP, sigtstp_handler);
+ } else
+ fputs(_("can't fork\n"), stderr);
+ set_tty(ctl);
+ puts("------------------------");
+ output_prompt(ctl, filename);
+}
+
+static void run_shell(struct more_control *ctl, char *filename)
+{
+ char cmdbuf[COMMAND_BUF];
+ int rc;
+ char *expanded;
+
+ kill_line(ctl);
+ putchar('!');
+ fflush(stdout);
+ ctl->prompt_len = 1;
+ if (ctl->run_previous_command)
+ fputs(ctl->shell_line, stdout);
+ else {
+ ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '!');
+ expanded = NULL;
+ rc = expand(ctl, &expanded, cmdbuf);
+ if (expanded) {
+ if (strlen(expanded) < sizeof(ctl->shell_line))
+ strcpy(ctl->shell_line, expanded);
+ else
+ rc = -1;
+ free(expanded);
+ }
+ if (rc < 0) {
+ fputs(_(" Overflow\n"), stderr);
+ output_prompt(ctl, filename);
+ return;
+ } else if (rc > 0) {
+ kill_line(ctl);
+ ctl->prompt_len = printf("!%s", ctl->shell_line);
+ }
+ }
+ fflush(stdout);
+ fputc('\n', stderr);
+ ctl->prompt_len = 0;
+ ctl->previous_shell = 1;
+ execute(ctl, filename, ctl->shell, ctl->shell, "-c", ctl->shell_line, 0);
+}
+
+/* Execute a colon-prefixed command. Returns <0 if not a command that
+ * should cause more of the file to be printed. */
+static int colon_command(struct more_control *ctl, char *filename, int cmd, int nlines)
+{
+ char ch;
+
+ if (cmd == 0)
+ ch = read_user_input(ctl);
+ else
+ ch = cmd;
+ ctl->last_colon_command = ch;
+ switch (ch) {
+ case 'f':
+ kill_line(ctl);
+ 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(stdout);
+ return -1;
+ case 'n':
+ if (nlines == 0) {
+ if (ctl->argv_position >= ctl->num_files - 1)
+ more_exit(0);
+ nlines++;
+ }
+ putchar('\r');
+ erase_prompt(ctl, 0);
+ change_file(ctl, nlines);
+ return 0;
+ case 'p':
+ if (ctl->no_tty_in) {
+ fputc(RINGBELL, stderr);
+ return -1;
+ }
+ putchar('\r');
+ erase_prompt(ctl, 0);
+ if (nlines == 0)
+ nlines++;
+ change_file(ctl, -nlines);
+ return 0;
+ case '!':
+ run_shell(ctl, filename);
+ return -1;
+ case 'q':
+ case 'Q':
+ more_exit(0);
+ default:
+ fputc(RINGBELL, stderr);
+ return -1;
+ }
+}
+
+/* Skip n lines in the file f */
+static void skip_lines(struct more_control *ctl, int n, FILE *f)
+{
+ int c;
+
+ while (n > 0) {
+ while ((c = more_getc(ctl, f)) != '\n')
+ if (c == EOF)
+ return;
+ n--;
+ 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, FILE *f)
+{
+ int c;
+ char *p;
+
+ prepare_line_buffer(ctl);
+
+ p = ctl->line_buf;
+ while ((c = more_getc(ctl, f)) != '\n' && c != EOF
+ && (size_t)(p - ctl->line_buf) < ctl->line_sz - 1)
+ *p++ = c;
+ if (c == '\n')
+ ctl->current_line++;
+ *p = '\0';
+}
+
+/* Search for nth occurrence of regular expression contained in buf in
+ * the file */
+static void search(struct more_control *ctl, char buf[], FILE *file, int n)
+{
+ long startline = ctl->file_position;
+ long line1 = startline;
+ long line2 = startline;
+ long line3;
+ int lncount;
+ int saveln, rc;
+ regex_t re;
+
+ 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);
+ }
+ while (!feof(file)) {
+ line3 = line2;
+ line2 = line1;
+ line1 = ctl->file_position;
+ read_line(ctl, file);
+ lncount++;
+ if (regexec(&re, ctl->line_buf, 0, NULL, 0) == 0) {
+ if (--n == 0) {
+ if (lncount > 3 || (lncount > 1 && ctl->no_tty_in)) {
+ 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 ? 3 : lncount);
+ more_fseek(ctl, file, line3);
+ if (ctl->no_scroll) {
+ if (ctl->clear_line_ends) {
+ putp(ctl->go_home);
+ putp(ctl->erase_line);
+ } else
+ more_clear_screen(ctl);
+ }
+ } else {
+ kill_line(ctl);
+ 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;
+ }
+ }
+ }
+ regfree(&re);
+ if (feof(file)) {
+ if (!ctl->no_tty_in) {
+ ctl->current_line = saveln;
+ more_fseek(ctl, file, startline);
+ } else {
+ fputs(_("\nPattern not found\n"), stdout);
+ more_exit(0);
+ }
+ free(ctl->previous_search);
+ ctl->previous_search = NULL;
+notfound:
+ more_error(ctl, _("Pattern not found"));
+ }
+}
+
+/* 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, FILE *f)
+{
+ int nlines;
+ int retval = 0;
+ int c;
+ char colonch;
+ int done;
+ char comchar, cmdbuf[INIT_BUF];
+
+ done = 0;
+ if (!ctl->report_errors)
+ output_prompt(ctl, filename);
+ else
+ ctl->report_errors = 0;
+ for (;;) {
+ nlines = read_number(ctl, &comchar);
+ ctl->run_previous_command = colonch = 0;
+ if (comchar == '.') { /* Repeat last command */
+ ctl->run_previous_command++;
+ comchar = ctl->last_key_command;
+ nlines = ctl->last_key_arg;
+ if (ctl->last_key_command == ':')
+ colonch = ctl->last_colon_command;
+ }
+ ctl->last_key_command = comchar;
+ ctl->last_key_arg = nlines;
+ if ((cc_t) comchar == ctl->output_tty.c_cc[VERASE]) {
+ kill_line(ctl);
+ output_prompt(ctl, filename);
+ continue;
+ }
+ switch (comchar) {
+ case ':':
+ retval = colon_command(ctl, filename, colonch, nlines);
+ if (retval >= 0)
+ done++;
+ break;
+ case 'b':
+ case ctrl('B'):
+ {
+ int initline;
+
+ if (ctl->no_tty_in) {
+ fputc(RINGBELL, stderr);
+ return -1;
+ }
+
+ if (nlines == 0)
+ nlines++;
+
+ putchar('\r');
+ erase_prompt(ctl, 0);
+ putchar('\n');
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ printf(P_("...back %d page",
+ "...back %d pages", nlines),
+ nlines);
+ if (ctl->clear_line_ends)
+ putp(ctl->erase_line);
+ putchar('\n');
+
+ initline = ctl->current_line - ctl->lines_per_screen * (nlines + 1);
+ if (!ctl->no_scroll)
+ --initline;
+ if (initline < 0)
+ initline = 0;
+ more_fseek(ctl, f, 0L);
+ ctl->current_line = 0; /* skip_lines() will make current_line correct */
+ skip_lines(ctl, initline, f);
+ if (!ctl->no_scroll) {
+ retval = ctl->lines_per_screen + 1;
+ } else {
+ retval = ctl->lines_per_screen;
+ }
+ done = 1;
+ break;
+ }
+ case ' ':
+ case 'z':
+ if (nlines == 0)
+ nlines = ctl->lines_per_screen;
+ else if (comchar == 'z')
+ ctl->lines_per_screen = nlines;
+ retval = nlines;
+ done = 1;
+ break;
+ case 'd':
+ case ctrl('D'):
+ if (nlines != 0)
+ ctl->d_scroll_len = nlines;
+ retval = ctl->d_scroll_len;
+ done = 1;
+ break;
+ case 'q':
+ case 'Q':
+ more_exit(0);
+ case 's':
+ case 'f':
+ case ctrl('F'):
+ if (nlines == 0)
+ nlines++;
+ if (comchar == 'f')
+ nlines *= ctl->lines_per_screen;
+ putchar('\r');
+ erase_prompt(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, f)) != '\n')
+ if (c == EOF) {
+ retval = 0;
+ done++;
+ goto endsw;
+ }
+ ctl->current_line++;
+ nlines--;
+ }
+ retval = ctl->lines_per_screen;
+ done = 1;
+ break;
+ case '\n':
+ if (nlines != 0)
+ ctl->lines_per_screen = nlines;
+ else
+ nlines = 1;
+ retval = nlines;
+ done = 1;
+ break;
+ case '\f':
+ if (!ctl->no_tty_in) {
+ more_clear_screen(ctl);
+ more_fseek(ctl, f, ctl->screen_start.row_num);
+ ctl->current_line = ctl->screen_start.line_num;
+ retval = ctl->lines_per_screen;
+ done = 1;
+ break;
+ } else {
+ fputc(RINGBELL, stderr);
+ break;
+ }
+ case '\'':
+ if (!ctl->no_tty_in) {
+ kill_line(ctl);
+ fputs(_("\n***Back***\n\n"), stdout);
+ more_fseek(ctl, f, ctl->context.row_num);
+ ctl->current_line = ctl->context.line_num;
+ retval = ctl->lines_per_screen;
+ done = 1;
+ break;
+ } else {
+ fputc(RINGBELL, stderr);
+ break;
+ }
+ case '=':
+ kill_line(ctl);
+ ctl->prompt_len = printf("%d", ctl->current_line);
+ fflush(stdout);
+ break;
+ case 'n':
+ if (!ctl->previous_search) {
+ more_error(ctl, _("No previous regular expression"));
+ break;
+ }
+ ctl->run_previous_command++;
+ /* fallthrough */
+ case '/':
+ if (nlines == 0)
+ nlines++;
+ kill_line(ctl);
+ putchar('/');
+ ctl->prompt_len = 1;
+ fflush(stdout);
+ if (ctl->run_previous_command) {
+ fputc('\r', stderr);
+ search(ctl, ctl->previous_search, f, nlines);
+ } else {
+ ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '/');
+ fputc('\r', stderr);
+ free(ctl->previous_search);
+ ctl->previous_search = xstrdup(cmdbuf);
+ search(ctl, cmdbuf, f, nlines);
+ }
+ retval = ctl->lines_per_screen - 1;
+ done = 1;
+ break;
+ case '!':
+ run_shell(ctl, filename);
+ break;
+ case '?':
+ case 'h':
+ if (ctl->no_scroll)
+ more_clear_screen(ctl);
+ fputs(_("\n"
+ "Most commands optionally preceded by integer argument k. "
+ "Defaults in brackets.\n"
+ "Star (*) indicates argument becomes new default.\n"), stdout);
+ puts("---------------------------------------"
+ "----------------------------------------");
+ fputs(_
+ ("<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 /usr/bin/vi 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"), stdout);
+ puts("---------------------------------------"
+ "----------------------------------------");
+ output_prompt(ctl, filename);
+ break;
+ case 'v': /* This case should go right before default */
+ if (!ctl->no_tty_in) {
+ /* Earlier: call vi +n file. This also
+ * works for emacs. POSIX: call vi -c n
+ * file (when editor is vi or ex). */
+ char *editor, *p;
+ int n = (ctl->current_line - ctl->lines_per_screen <= 0 ? 1 :
+ ctl->current_line - (ctl->lines_per_screen + 1) / 2);
+ int split = 0;
+
+ editor = getenv("VISUAL");
+ if (editor == NULL || *editor == '\0')
+ editor = getenv("EDITOR");
+ if (editor == NULL || *editor == '\0')
+ editor = VI;
+
+ p = strrchr(editor, '/');
+ if (p)
+ p++;
+ else
+ p = editor;
+ if (!strcmp(p, "vi") || !strcmp(p, "ex")) {
+ sprintf(cmdbuf, "-c %d", n);
+ split = 1;
+ } else {
+ sprintf(cmdbuf, "+%d", n);
+ }
+
+ kill_line(ctl);
+ 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);
+ break;
+ }
+ /* fallthrough */
+ default:
+ if (ctl->suppress_bell) {
+ kill_line(ctl);
+ if (ctl->enter_std && ctl->exit_std) {
+ putp(ctl->enter_std);
+ ctl->prompt_len =
+ printf(_
+ ("[Press 'h' for instructions.]"))
+ + 2 * ctl->stdout_glitch;
+ putp(ctl->exit_std);
+ } else
+ ctl->prompt_len =
+ printf(_
+ ("[Press 'h' for instructions.]"));
+ fflush(stdout);
+ } else
+ fputc(RINGBELL, stderr);
+ break;
+ }
+ if (done)
+ break;
+ }
+ putchar('\r');
+ endsw:
+ ctl->waiting_input = 0;
+ 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, FILE *f, 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, f, &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_prompt(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);
+ print_buf(ctl, ctl->line_buf, length);
+ if (nchars < ctl->prompt_len)
+ erase_prompt(ctl, nchars); /* erase_prompt () sets prompt_len to 0 */
+ else
+ ctl->prompt_len = 0;
+ if (nchars < ctl->num_columns || !ctl->fold_long_lines)
+ print_buf(ctl, "\n", 1); /* will turn off UL if necessary */
+ num_lines--;
+ }
+ if (ctl->underline_state) {
+ putp(ctl->exit_underline);
+ ctl->underline_state = 0;
+ }
+ fflush(stdout);
+ if ((c = more_getc(ctl, f)) == 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, f);
+ sigsetjmp(ctl->destination, 1);
+ ctl->is_paused = 0;
+ ctl->starting_up = 0;
+ if ((num_lines = more_key_command(ctl, NULL, f)) == 0)
+ return;
+ if (ctl->hard_tty && ctl->prompt_len > 0)
+ erase_prompt(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;
+ }
+}
+
+/* Come here if a signal for a window size change is received */
+static void sigwinch_handler(int dummy __attribute__((__unused__)))
+{
+ struct winsize win;
+
+ signal(SIGWINCH, SIG_IGN);
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1) {
+ if (win.ws_row != 0) {
+ global_ctl->lines_per_page = win.ws_row;
+ global_ctl->d_scroll_len = global_ctl->lines_per_page / 2 - 1;
+ if (global_ctl->d_scroll_len <= 0)
+ global_ctl->d_scroll_len = 1;
+ global_ctl->lines_per_screen = global_ctl->lines_per_page - 1;
+ }
+ if (win.ws_col != 0)
+ global_ctl->num_columns = win.ws_col;
+ }
+ signal(SIGWINCH, sigwinch_handler);
+}
+
+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 initterm(struct more_control *ctl)
+{
+ int ret;
+ char *term;
+ struct winsize win;
+
+#ifndef NON_INTERACTIVE_MORE
+ ctl->no_tty_out = tcgetattr(STDOUT_FILENO, &ctl->output_tty);
+#endif
+ if (!ctl->no_tty_out) {
+ 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;
+ ctl->enable_underlining = 0;
+ }
+ setupterm(term, 1, &ret);
+ if (ret <= 0) {
+ ctl->dumb_tty = 1;
+ ctl->enable_underlining = 0;
+ } else {
+ 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);
+ ctl->enter_std = tigetstr(TERM_STANDARD_MODE);
+ ctl->exit_std = tigetstr(TERM_EXIT_STANDARD_MODE);
+ if (0 < tigetnum(TERM_STD_MODE_GLITCH))
+ ctl->stdout_glitch = 1;
+
+ /* Set up for underlining: some terminals don't
+ * need it; others have start/stop sequences,
+ * still others have an underline char sequence
+ * which is assumed to move the cursor forward
+ * one character. If underline sequence isn't
+ * available, settle for standout sequence. */
+ if (tigetflag(TERM_UNDERLINE)
+ || tigetflag(TERM_OVER_STRIKE))
+ ctl->enable_underlining = 0;
+ if ((ctl->underline_ch = tigetstr(TERM_UNDERLINE_CHAR)) == NULL)
+ ctl->underline_ch = "";
+ if (((ctl->enter_underline =
+ tigetstr(TERM_ENTER_UNDERLINE)) == NULL
+ || (ctl->exit_underline =
+ tigetstr(TERM_EXIT_UNDERLINE)) == NULL)
+ && !*ctl->underline_ch) {
+ if ((ctl->enter_underline = ctl->enter_std) == NULL
+ || (ctl->exit_underline = ctl->exit_std) == NULL) {
+ ctl->enter_underline = "";
+ ctl->exit_underline = "";
+ } else
+ ctl->underline_glitch = ctl->stdout_glitch;
+ } else {
+ ctl->underline_glitch = 0;
+ }
+ ctl->go_home = tigetstr(TERM_HOME);
+ if (ctl->go_home == NULL || *ctl->go_home == '\0') {
+ if ((ctl->cursor_addr =
+ tigetstr(TERM_CURSOR_ADDRESS)) != NULL) {
+ const char *t =
+ (const char *)tparm(ctl->cursor_addr, 0,
+ 0);
+ xstrncpy(ctl->home_position, t,
+ sizeof(ctl->home_position));
+ ctl->go_home = ctl->home_position;
+ }
+ }
+ ctl->clear_rest = tigetstr(TERM_CLEAR_TO_SCREEN_END);
+ if ((ctl->backspace_ch = tigetstr(TERM_LINE_DOWN)) == NULL)
+ ctl->backspace_ch = BS;
+
+ }
+ if ((ctl->shell = getenv("SHELL")) == NULL)
+ ctl->shell = "/bin/sh";
+ }
+ 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) {
+ ctl->output_tty.c_lflag &= ~(ICANON | ECHO);
+ ctl->output_tty.c_cc[VMIN] = 1;
+ ctl->output_tty.c_cc[VTIME] = 0;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ FILE *f;
+ char *s;
+ int chr;
+ int left;
+ int print_names = 0;
+ int init = 0;
+ int search_at_start = 0;
+ int skip_file = 0;
+ int start_at_line = 0;
+ char *initbuf = NULL;
+ struct more_control ctl = {
+ .first_file = 1,
+ .fold_long_lines = 1,
+ .no_quit_dialog = 1,
+ .starting_up = 1,
+ .stop_after_formfeed = 1,
+ .enable_underlining = 1,
+ .wrap_margin = 1,
+ .lines_per_page = LINES_PER_PAGE,
+ .num_columns = NUM_COLUMNS,
+ .d_scroll_len = SCROLL_LEN,
+ 0
+ };
+ global_ctl = &ctl;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ if (argc > 1) {
+ /* first arg may be one of our standard longopts */
+ if (!strcmp(argv[1], "--help"))
+ usage();
+ if (!strcmp(argv[1], "--version")) {
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ ctl.num_files = argc;
+ ctl.file_names = argv;
+ setlocale(LC_ALL, "");
+ initterm(&ctl);
+
+ /* Auto set no scroll on when binary is called page */
+ if (!(strcmp(program_invocation_short_name, "page")))
+ ctl.no_scroll++;
+
+ 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;
+
+ if ((s = getenv("MORE")) != NULL)
+ arg_parser(&ctl, s);
+
+ while (--ctl.num_files > 0) {
+ if ((chr = (*++ctl.file_names)[0]) == '-') {
+ arg_parser(&ctl, *ctl.file_names + 1);
+ } else if (chr == '+') {
+ s = *ctl.file_names;
+ if (*++s == '/') {
+ search_at_start++;
+ initbuf = xstrdup(s + 1);
+ } else {
+ init++;
+ for (start_at_line = 0; *s != '\0'; s++)
+ if (isdigit(*s))
+ start_at_line =
+ start_at_line * 10 + *s - '0';
+ --start_at_line;
+ }
+ } else
+ break;
+ }
+ /* 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)
+ print_names++;
+ if (!ctl.no_tty_in && ctl.num_files == 0) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ } else
+ f = stdin;
+ if (!ctl.no_tty_out) {
+ signal(SIGQUIT, sigquit_handler);
+ signal(SIGINT, more_exit);
+ signal(SIGWINCH, sigwinch_handler);
+ if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) {
+ signal(SIGTSTP, sigtstp_handler);
+ ctl.catch_suspend++;
+ }
+ tcsetattr(STDERR_FILENO, TCSANOW, &ctl.output_tty);
+ }
+ if (ctl.no_tty_in) {
+ if (ctl.no_tty_out)
+ copy_file(stdin);
+ else {
+ if ((chr = getc(f)) == '\f')
+ more_clear_screen(&ctl);
+ else {
+ ungetc(chr, f);
+ if (ctl.no_scroll && (chr != EOF)) {
+ if (ctl.clear_line_ends)
+ putp(ctl.go_home);
+ else
+ more_clear_screen(&ctl);
+ }
+ }
+ if (search_at_start) {
+ free(ctl.previous_search);
+ ctl.previous_search = xstrdup(initbuf);
+ search(&ctl, initbuf, stdin, 1);
+ if (ctl.no_scroll)
+ left--;
+ } else if (init)
+ skip_lines(&ctl, start_at_line, stdin);
+ screen(&ctl, stdin, left);
+ }
+ ctl.no_tty_in = 0;
+ print_names++;
+ ctl.first_file = 0;
+ }
+
+ while (ctl.argv_position < ctl.num_files) {
+ if ((f = checkf(&ctl, ctl.file_names[ctl.argv_position], &skip_file)) != NULL) {
+ ctl.context.line_num = ctl.context.row_num = 0;
+ ctl.current_line = 0;
+ if (ctl.first_file)
+ sigsetjmp(ctl.destination, 1);
+ if (ctl.first_file) {
+ ctl.first_file = 0;
+ if (search_at_start) {
+ free(ctl.previous_search);
+ ctl.previous_search = xstrdup(initbuf);
+ search(&ctl, initbuf, f, 1);
+ if (ctl.no_scroll)
+ left--;
+ } else if (init)
+ skip_lines(&ctl, start_at_line, f);
+ } else if (ctl.argv_position < ctl.num_files && !ctl.no_tty_out) {
+ sigsetjmp(ctl.destination, 1);
+ left = more_key_command(&ctl, ctl.file_names[ctl.argv_position], f);
+ }
+ if (left != 0) {
+ if ((ctl.no_scroll || skip_file)
+ && (ctl.file_size != LONG_MAX)) {
+ if (ctl.clear_line_ends)
+ putp(ctl.go_home);
+ else
+ more_clear_screen(&ctl);
+ }
+ if (print_names) {
+ if (ctl.bad_stdout)
+ erase_prompt(&ctl, 0);
+ if (ctl.clear_line_ends)
+ putp(ctl.erase_line);
+ fputs("::::::::::::::", stdout);
+ if (ctl.prompt_len > 14)
+ erase_prompt(&ctl, 14);
+ putchar('\n');
+ if (ctl.clear_line_ends)
+ putp(ctl.erase_line);
+ puts(ctl.file_names[ctl.argv_position]);
+ if (ctl.clear_line_ends)
+ putp(ctl.erase_line);
+ fputs("::::::::::::::\n", stdout);
+ if (left > ctl.lines_per_page - 4)
+ left = ctl.lines_per_page - 4;
+ }
+ if (ctl.no_tty_out)
+ copy_file(f);
+ else {
+ ctl.within_file = 1;
+ screen(&ctl, f, left);
+ ctl.within_file = 0;
+ }
+ }
+ sigsetjmp(ctl.destination, 1);
+ fflush(stdout);
+ fclose(f);
+ ctl.screen_start.line_num = ctl.screen_start.row_num = 0L;
+ ctl.context.line_num = ctl.context.row_num = 0L;
+ }
+ ctl.argv_position++;
+ ctl.first_file = 0;
+ }
+ free(ctl.previous_search);
+ free(initbuf);
+ free(ctl.line_buf);
+ reset_tty();
+ exit(EXIT_SUCCESS);
+}
diff --git a/text-utils/pg.1 b/text-utils/pg.1
new file mode 100644
index 0000000..901ac7d
--- /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 VARIABLES"
+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 "SEE ALSO"
+.BR cat (1),
+.BR more (1),
+.BR sh (1),
+.BR terminfo (5),
+.BR locale (7),
+.BR regex (7),
+.BR term (7)
+.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 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..f441c04
--- /dev/null
+++ b/text-utils/pg.c
@@ -0,0 +1,1691 @@
+/*
+ * 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, READBUF)) == (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;
+ } else {
+ 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;
+ } else {
+ 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;
+ } else 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, 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);
+ atexit(close_stdout);
+
+ 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")) {
+ printf(UTIL_LINUX_VERSION);
+ 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':
+ printf(UTIL_LINUX_VERSION);
+ return 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..e6e1f09
--- /dev/null
+++ b/text-utils/rev.1
@@ -0,0 +1,59 @@
+.\" 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.
+.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..13a41f6
--- /dev/null
+++ b/text-utils/rev.c
@@ -0,0 +1,183 @@
+/*-
+ * 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;
+
+ 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);
+ atexit(close_stdout);
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch(ch) {
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ exit(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++;
+ }
+
+ 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);
+ }
+ if (ferror(fp)) {
+ warn("%s", filename);
+ rval = EXIT_FAILURE;
+ }
+ 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..64f7f8d
--- /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 (1)).
+.SH SEE ALSO
+.BR colcrt (1),
+.BR login (1),
+.BR man (1),
+.BR nroff (1),
+.BR setenv (1),
+.BR terminfo (5)
+.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 HISTORY
+The
+.B ul
+command appeared in 3.0BSD.
+.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..b5ce4db
--- /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;
+ else
+ 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);
+ atexit(close_stdout);
+
+ 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':
+ printf(UTIL_LINUX_VERSION);
+ return 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 = xmalloc((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 = xmalloc((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);
+}