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