diff options
Diffstat (limited to 'text-utils')
36 files changed, 11993 insertions, 0 deletions
diff --git a/text-utils/Makemodule.am b/text-utils/Makemodule.am new file mode 100644 index 0000000..af1bf02 --- /dev/null +++ b/text-utils/Makemodule.am @@ -0,0 +1,108 @@ +if BUILD_COL +usrbin_exec_PROGRAMS += col +MANPAGES += text-utils/col.1 +dist_noinst_DATA += text-utils/col.1.adoc +col_SOURCES = text-utils/col.c +col_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_COLCRT +usrbin_exec_PROGRAMS += colcrt +MANPAGES += text-utils/colcrt.1 +dist_noinst_DATA += text-utils/colcrt.1.adoc +colcrt_SOURCES = text-utils/colcrt.c +endif + +if BUILD_COLRM +usrbin_exec_PROGRAMS += colrm +MANPAGES += text-utils/colrm.1 +dist_noinst_DATA += text-utils/colrm.1.adoc +colrm_SOURCES = text-utils/colrm.c +colrm_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_COLUMN +usrbin_exec_PROGRAMS += column +MANPAGES += text-utils/column.1 +dist_noinst_DATA += text-utils/column.1.adoc +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 +MANPAGES += text-utils/hexdump.1 +dist_noinst_DATA += text-utils/hexdump.1.adoc +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 +MANPAGES += text-utils/rev.1 +dist_noinst_DATA += text-utils/rev.1.adoc +rev_SOURCES = text-utils/rev.c +endif + +if BUILD_LINE +usrbin_exec_PROGRAMS += line +MANPAGES += text-utils/line.1 +dist_noinst_DATA += text-utils/line.1.adoc +line_SOURCES = text-utils/line.c +endif + +if BUILD_PG +usrbin_exec_PROGRAMS += pg +MANPAGES += text-utils/pg.1 +dist_noinst_DATA += text-utils/pg.1.adoc +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 +MANPAGES += text-utils/ul.1 +dist_noinst_DATA += text-utils/ul.1.adoc +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 +MANPAGES += text-utils/more.1 +dist_noinst_DATA += text-utils/more.1.adoc +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..c18d4b2 --- /dev/null +++ b/text-utils/col.1 @@ -0,0 +1,163 @@ +'\" t +.\" Title: col +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-30 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "COL" "1" "2023-11-30" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +col \- filter reverse line feeds from input +.SH "SYNOPSIS" +.sp +\fBcol\fP \fIoptions\fP +.SH "DESCRIPTION" +.sp +\fBcol\fP 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 \fBnroff\fP(1) and \fBtbl\fP(1). +.sp +\fBcol\fP reads from standard input and writes to standard output. +.SH "OPTIONS" +.sp +\fB\-b\fP, \fB\-\-no\-backspaces\fP +.RS 4 +Do not output any backspaces, printing only the last character written to each column position. +.RE +.sp +\fB\-f\fP, \fB\-\-fine\fP +.RS 4 +Permit half\-forward line feeds. Normally characters destined for a half\-line boundary are printed on the following line. +.RE +.sp +\fB\-h\fP, \fB\-\-tabs\fP +.RS 4 +Output tabs instead of multiple spaces. +.RE +.sp +\fB\-l\fP, \fB\-\-lines\fP \fInumber\fP +.RS 4 +Buffer at least \fInumber\fP lines in memory. By default, 128 lines are buffered. +.RE +.sp +\fB\-p\fP, \fB\-\-pass\fP +.RS 4 +Force unknown control sequences to be passed through unchanged. Normally \fBcol\fP will filter out any control sequences other than those recognized and interpreted by itself, which are listed below. +.RE +.sp +\fB\-x\fP, \fB\-\-spaces\fP +.RS 4 +Output multiple spaces instead of tabs. +.RE +.sp +\fB\-H\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "CONFORMING TO" +.sp +The \fBcol\fP utility conforms to the Single UNIX Specification, Version 2. The \fB\-l\fP option is an extension to the standard. +.SH "NOTES" +.sp +The control sequences for carriage motion that \fBcol\fP understands and their decimal values are listed in the following table: +.sp +\fBESC\-7\fP +.RS 4 +reverse line feed (escape then 7) +.RE +.sp +\fBESC\-8\fP +.RS 4 +half reverse line feed (escape then 8) +.RE +.sp +\fBESC\-9\fP +.RS 4 +half forward line feed (escape then 9) +.RE +.sp +\fBbackspace\fP +.RS 4 +moves back one column (8); ignored in the first column +.RE +.sp +\fBnewline\fP +.RS 4 +forward line feed (10); also does carriage return +.RE +.sp +\fBcarriage return\fP +.RS 4 +(13) +.RE +.sp +\fBshift in\fP +.RS 4 +shift to normal character set (15) +.RE +.sp +\fBshift out\fP +.RS 4 +shift to alternate character set (14) +.RE +.sp +\fBspace\fP +.RS 4 +moves forward one column (32) +.RE +.sp +\fBtab\fP +.RS 4 +moves forward to next tab stop (9) +.RE +.sp +\fBvertical tab\fP +.RS 4 +reverse line feed (11) +.RE +.sp +All unrecognized control characters and escape sequences are discarded. +.sp +\fBcol\fP keeps track of the character set as characters are read and makes sure the character set is correct when they are output. +.sp +If the input attempts to back up to the last flushed line, \fBcol\fP will display a warning message. +.SH "HISTORY" +.sp +A \fBcol\fP command appeared in Version 6 AT&T UNIX. +.SH "SEE ALSO" +.sp +\fBexpand\fP(1), +\fBnroff\fP(1), +\fBtbl\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBcol\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/col.1.adoc b/text-utils/col.1.adoc new file mode 100644 index 0000000..46610f0 --- /dev/null +++ b/text-utils/col.1.adoc @@ -0,0 +1,139 @@ +//po4a: entry man manual +//// +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 +//// += col(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: col + +== NAME + +col - filter reverse line feeds from input + +== SYNOPSIS + +*col* _options_ + +== DESCRIPTION + +*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 *nroff*(1) and *tbl*(1). + +*col* reads from standard input and writes to standard output. + +== OPTIONS + +*-b*, *--no-backspaces*:: +Do not output any backspaces, printing only the last character written to each column position. + +*-f*, *--fine*:: +Permit half-forward line feeds. Normally characters destined for a half-line boundary are printed on the following line. + +*-h*, *--tabs*:: +Output tabs instead of multiple spaces. + +*-l*, *--lines* _number_:: +Buffer at least _number_ lines in memory. By default, 128 lines are buffered. + +*-p*, *--pass*:: +Force unknown control sequences to be passed through unchanged. Normally *col* will filter out any control sequences other than those recognized and interpreted by itself, which are listed below. + +*-x*, *--spaces*:: +Output multiple spaces instead of tabs. + +*-H*, *--help*:: +Display help text and exit. + +*-V*, *--version*:: +Print version and exit. + +== CONFORMING TO + +The *col* utility conforms to the Single UNIX Specification, Version 2. The *-l* option is an extension to the standard. + +== NOTES + +The control sequences for carriage motion that *col* understands and their decimal values are listed in the following table: + +*ESC-7*:: +reverse line feed (escape then 7) +*ESC-8*:: +half reverse line feed (escape then 8) +*ESC-9*:: +half forward line feed (escape then 9) +*backspace*:: +moves back one column (8); ignored in the first column +*newline*:: +forward line feed (10); also does carriage return +*carriage return*:: +(13) +*shift in*:: +shift to normal character set (15) +*shift out*:: +shift to alternate character set (14) +*space*:: +moves forward one column (32) +*tab*:: +moves forward to next tab stop (9) +*vertical tab*:: +reverse line feed (11) + +All unrecognized control characters and escape sequences are discarded. + +*col* keeps track of the character set as characters are read and makes sure the character set is correct when they are output. + +If the input attempts to back up to the last flushed line, *col* will display a warning message. + +== HISTORY + +A *col* command appeared in Version 6 AT&T UNIX. + +== SEE ALSO + +*expand*(1), +*nroff*(1), +*tbl*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/col.c b/text-utils/col.c new file mode 100644 index 0000000..56ccec9 --- /dev/null +++ b/text-utils/col.c @@ -0,0 +1,723 @@ +/*- + * 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 <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "optutils.h" +#include "strutils.h" +#include "widechar.h" +#include "xalloc.h" + +#define SPACE ' ' /* space */ +#define BS '\b' /* backspace */ +#define NL '\n' /* newline */ +#define CR '\r' /* carriage return */ +#define TAB '\t' /* tab */ +#define VT '\v' /* vertical tab (aka reverse line feed) */ + +#define ESC '\033' /* escape */ +#define RLF '\a' /* ESC-007 reverse line feed */ +#define RHLF BS /* ESC-010 reverse half-line feed */ +#define FHLF TAB /* ESC-011 forward half-line feed */ + +#define SO '\016' /* activate the G1 character set */ +#define SI '\017' /* activate the G0 character set */ + +/* build up at least this many lines before flushing them out */ +#define BUFFER_MARGIN 32 + +/* number of lines to allocate */ +#define NALLOC 64 + +#if HAS_FEATURE_ADDRESS_SANITIZER || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +# define COL_DEALLOCATE_ON_EXIT +#endif + +/* SI & SO charset mode */ +enum { + CS_NORMAL, + CS_ALTERNATE +}; + +struct col_char { + size_t c_column; /* column character is in */ + wchar_t c_char; /* character in question */ + int c_width; /* character width */ + + uint8_t c_set:1; /* character set (currently only 2) */ +}; + +struct col_line { + struct col_char *l_line; /* characters on the line */ + struct col_line *l_prev; /* previous line */ + struct col_line *l_next; /* next line */ + size_t l_lsize; /* allocated sizeof l_line */ + size_t l_line_len; /* strlen(l_line) */ + size_t l_max_col; /* max column in the line */ + + uint8_t l_needs_sort:1; /* set if chars went in out of order */ +}; + +#ifdef COL_DEALLOCATE_ON_EXIT +/* + * Free memory before exit when compiling LeakSanitizer. + */ +struct col_alloc { + struct col_line *l; + struct col_alloc *next; +}; +#endif + +struct col_ctl { + struct col_line *lines; + struct col_line *l; /* current line */ + size_t max_bufd_lines; /* max # lines to keep in memory */ + struct col_line *line_freelist; + size_t nblank_lines; /* # blanks after last flushed line */ +#ifdef COL_DEALLOCATE_ON_EXIT + struct col_alloc *alloc_root; /* first of line allocations */ + struct col_alloc *alloc_head; /* latest line allocation */ +#endif + unsigned int + last_set:1, /* char_set of last char printed */ + compress_spaces:1, /* if doing space -> tab conversion */ + fine:1, /* if `fine' resolution (half lines) */ + no_backspaces:1, /* if not to output any backspaces */ + pass_unknown_seqs:1; /* whether to pass unknown control sequences */ +}; + +struct col_lines { + struct col_char *c; + wint_t ch; + size_t adjust; + size_t cur_col; + ssize_t cur_line; + size_t extra_lines; + size_t max_line; + size_t nflushd_lines; + size_t this_line; + + unsigned int + cur_set:1, + warned:1; +}; + +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 from standard input.\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); + + printf(USAGE_MAN_TAIL("col(1)")); + exit(EXIT_SUCCESS); +} + +static inline void col_putchar(wchar_t ch) +{ + if (putwchar(ch) == WEOF) + err(EXIT_FAILURE, _("write failed")); +} + +/* + * 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. + */ +static void flush_blanks(struct col_ctl *ctl) +{ + int half = 0; + ssize_t i, nb = ctl->nblank_lines; + + if (nb & 1) { + if (ctl->fine) + half = 1; + else + nb++; + } + nb /= 2; + for (i = nb; --i >= 0;) + col_putchar(NL); + + if (half) { + col_putchar(ESC); + col_putchar('9'); + if (!nb) + col_putchar(CR); + } + ctl->nblank_lines = 0; +} + +/* + * Write a line to stdout taking care of space to tab conversion (-h flag) + * and character set shifts. + */ +static void flush_line(struct col_ctl *ctl, struct col_line *l) +{ + struct col_char *c, *endc; + size_t nchars = l->l_line_len, last_col = 0, this_col; + + if (l->l_needs_sort) { + static struct col_char *sorted = NULL; + static size_t count_size = 0, *count = NULL, sorted_size = 0; + size_t 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 (sorted_size < l->l_lsize) { + sorted_size = l->l_lsize; + sorted = xrealloc(sorted, sizeof(struct col_char) * sorted_size); + } + if (count_size <= l->l_max_col) { + count_size = l->l_max_col + 1; + count = xrealloc(count, sizeof(size_t) * count_size); + } + memset(count, 0, sizeof(size_t) * l->l_max_col + 1); + for (i = nchars, c = l->l_line; c && 0 < i; i--, 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++) { + size_t save = count[i]; + count[i] = tot; + tot += save; + } + + for (i = nchars, c = l->l_line; 0 < i; i--, c++) + sorted[count[c->c_column]++] = *c; + c = sorted; + } else + c = l->l_line; + + while (0 < nchars) { + this_col = c->c_column; + endc = c; + + /* find last character */ + do { + ++endc; + } while (0 < --nchars && this_col == endc->c_column); + + if (ctl->no_backspaces) { + /* print only the last character */ + c = endc - 1; + if (0 < nchars && endc->c_column < this_col + c->c_width) + continue; + } + + if (last_col < this_col) { + /* tabs and spaces handling */ + ssize_t nspace = this_col - last_col; + + if (ctl->compress_spaces && 1 < nspace) { + ssize_t ntabs; + + ntabs = this_col / 8 - last_col / 8; + if (0 < ntabs) { + nspace = this_col & 7; + while (0 <= --ntabs) + col_putchar(TAB); + } + } + while (0 <= --nspace) + col_putchar(SPACE); + last_col = this_col; + } + + for (;;) { + /* SO / SI character set changing */ + if (c->c_set != ctl->last_set) { + switch (c->c_set) { + case CS_NORMAL: + col_putchar(SI); + break; + case CS_ALTERNATE: + col_putchar(SO); + break; + default: + abort(); + } + ctl->last_set = c->c_set; + } + + /* output a character */ + col_putchar(c->c_char); + + /* rubout control chars from output */ + if (c + 1 < endc) { + int i; + + for (i = 0; i < c->c_width; i++) + col_putchar(BS); + } + + if (endc <= ++c) + break; + } + last_col += (c - 1)->c_width; + } +} + +static struct col_line *alloc_line(struct col_ctl *ctl) +{ + struct col_line *l; + size_t i; + + if (!ctl->line_freelist) { + l = xmalloc(sizeof(struct col_line) * NALLOC); +#ifdef COL_DEALLOCATE_ON_EXIT + if (ctl->alloc_root == NULL) { + ctl->alloc_root = xcalloc(1, sizeof(struct col_alloc)); + ctl->alloc_root->l = l; + ctl->alloc_head = ctl->alloc_root; + } else { + ctl->alloc_head->next = xcalloc(1, sizeof(struct col_alloc)); + ctl->alloc_head = ctl->alloc_head->next; + ctl->alloc_head->l = l; + } +#endif + ctl->line_freelist = l; + for (i = 1; i < NALLOC; i++, l++) + l->l_next = l + 1; + l->l_next = NULL; + } + l = ctl->line_freelist; + ctl->line_freelist = l->l_next; + + memset(l, 0, sizeof(struct col_line)); + return l; +} + +static void free_line(struct col_ctl *ctl, struct col_line *l) +{ + l->l_next = ctl->line_freelist; + ctl->line_freelist = l; +} + +static void flush_lines(struct col_ctl *ctl, ssize_t nflush) +{ + struct col_line *l; + + while (0 <= --nflush) { + l = ctl->lines; + ctl->lines = l->l_next; + if (l->l_line) { + flush_blanks(ctl); + flush_line(ctl, l); + } + ctl->nblank_lines++; + free(l->l_line); + free_line(ctl, l); + } + if (ctl->lines) + ctl->lines->l_prev = NULL; +} + +static int handle_not_graphic(struct col_ctl *ctl, struct col_lines *lns) +{ + switch (lns->ch) { + case BS: + if (lns->cur_col == 0) + return 1; /* can't go back further */ + if (lns->c) + lns->cur_col -= lns->c->c_width; + else + lns->cur_col -= 1; + return 1; + case CR: + lns->cur_col = 0; + return 1; + case ESC: + switch (getwchar()) { /* just ignore EOF */ + case RLF: + lns->cur_line -= 2; + break; + case RHLF: + lns->cur_line -= 1; + break; + case FHLF: + lns->cur_line += 1; + if (0 < lns->cur_line && lns->max_line < (size_t)lns->cur_line) + lns->max_line = lns->cur_line; + break; + default: + break; + } + return 1; + case NL: + lns->cur_line += 2; + if (0 < lns->cur_line && lns->max_line < (size_t)lns->cur_line) + lns->max_line = lns->cur_line; + lns->cur_col = 0; + return 1; + case SPACE: + lns->cur_col += 1; + return 1; + case SI: + lns->cur_set = CS_NORMAL; + return 1; + case SO: + lns->cur_set = CS_ALTERNATE; + return 1; + case TAB: /* adjust column */ + lns->cur_col |= 7; + lns->cur_col += 1; + return 1; + case VT: + lns->cur_line -= 2; + return 1; + default: + break; + } + if (iswspace(lns->ch)) { + if (0 < wcwidth(lns->ch)) + lns->cur_col += wcwidth(lns->ch); + return 1; + } + + if (!ctl->pass_unknown_seqs) + return 1; + return 0; +} + +static void update_cur_line(struct col_ctl *ctl, struct col_lines *lns) +{ + ssize_t nmove; + + lns->adjust = 0; + nmove = lns->cur_line - lns->this_line; + if (!ctl->fine) { + /* round up to next line */ + if (lns->cur_line & 1) { + lns->adjust = 1; + nmove++; + } + } + if (nmove < 0) { + for (; nmove < 0 && ctl->l->l_prev; nmove++) + ctl->l = ctl->l->l_prev; + + if (nmove) { + if (lns->nflushd_lines == 0) { + /* + * Allow backup past first line if nothing + * has been flushed yet. + */ + for (; nmove < 0; nmove++) { + struct col_line *lnew = alloc_line(ctl); + ctl->l->l_prev = lnew; + lnew->l_next = ctl->l; + ctl->l = ctl->lines = lnew; + lns->extra_lines += 1; + } + } else { + if (!lns->warned) { + warnx(_("warning: can't back up %s."), + lns->cur_line < 0 ? + _("past first line") : + _("-- line already flushed")); + lns->warned = 1; + } + lns->cur_line -= nmove; + } + } + } else { + /* may need to allocate here */ + for (; 0 < nmove && ctl->l->l_next; nmove--) + ctl->l = ctl->l->l_next; + + for (; 0 < nmove; nmove--) { + struct col_line *lnew = alloc_line(ctl); + lnew->l_prev = ctl->l; + ctl->l->l_next = lnew; + ctl->l = lnew; + } + } + + lns->this_line = lns->cur_line + lns->adjust; + nmove = lns->this_line - lns->nflushd_lines; + + if (0 < nmove && ctl->max_bufd_lines + BUFFER_MARGIN <= (size_t)nmove) { + lns->nflushd_lines += nmove - ctl->max_bufd_lines; + flush_lines(ctl, nmove - ctl->max_bufd_lines); + } +} + +static void parse_options(struct col_ctl *ctl, int argc, char **argv) +{ + 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 } + }; + static const ul_excl_t excl[] = { + { 'h', 'x' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + int opt; + + while ((opt = getopt_long(argc, argv, "bfhl:pxVH", longopts, NULL)) != -1) { + err_exclusive_options(opt, longopts, excl, excl_st); + + switch (opt) { + case 'b': /* do not output backspaces */ + ctl->no_backspaces = 1; + break; + case 'f': /* allow half forward line feeds */ + ctl->fine = 1; + break; + case 'h': /* compress spaces into tabs */ + ctl->compress_spaces = 1; + break; + case 'l': + /* + * Buffered line count, which is a value in half + * lines e.g. twice the amount specified. + */ + ctl->max_bufd_lines = strtou32_or_err(optarg, _("bad -l argument")) * 2; + break; + case 'p': + ctl->pass_unknown_seqs = 1; + break; + case 'x': /* do not compress spaces into tabs */ + ctl->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); + } +} + +#ifdef COL_DEALLOCATE_ON_EXIT +static void free_line_allocations(struct col_alloc *root) +{ + struct col_alloc *next; + + while (root) { + next = root->next; + free(root->l); + free(root); + root = next; + } +} +#endif + +static void process_char(struct col_ctl *ctl, struct col_lines *lns) +{ + /* Deal printable characters */ + if (!iswgraph(lns->ch) && handle_not_graphic(ctl, lns)) + return; + + /* Must stuff ch in a line - are we at the right one? */ + if ((size_t)lns->cur_line != lns->this_line - lns->adjust) + update_cur_line(ctl, lns); + + /* Does line buffer need to grow? */ + if (ctl->l->l_lsize <= ctl->l->l_line_len + 1) { + size_t need; + + need = ctl->l->l_lsize ? ctl->l->l_lsize * 2 : NALLOC; + ctl->l->l_line = xrealloc(ctl->l->l_line, need * sizeof(struct col_char)); + ctl->l->l_lsize = need; + } + + /* Store character */ + lns->c = &ctl->l->l_line[ctl->l->l_line_len++]; + lns->c->c_char = lns->ch; + lns->c->c_set = lns->cur_set; + + if (0 < lns->cur_col) + lns->c->c_column = lns->cur_col; + else + lns->c->c_column = 0; + lns->c->c_width = wcwidth(lns->ch); + + /* + * If things are put in out of order, they will need sorting + * when it is flushed. + */ + if (lns->cur_col < ctl->l->l_max_col) + ctl->l->l_needs_sort = 1; + else + ctl->l->l_max_col = lns->cur_col; + if (0 < lns->c->c_width) + lns->cur_col += lns->c->c_width; + +} + +int main(int argc, char **argv) +{ + struct col_ctl ctl = { + .compress_spaces = 1, + .last_set = CS_NORMAL, + .max_bufd_lines = BUFFER_MARGIN * 2, + }; + struct col_lines lns = { + .cur_set = CS_NORMAL, + }; + int ret = EXIT_SUCCESS; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + ctl.lines = ctl.l = alloc_line(&ctl); + + parse_options(&ctl, argc, argv); + + while (feof(stdin) == 0) { + errno = 0; + /* Get character */ + lns.ch = getwchar(); + + if (lns.ch == WEOF) { + if (errno == EILSEQ) { + /* Illegal multibyte sequence */ + int c; + char buf[5]; + size_t len, i; + + c = getchar(); + if (c == EOF) + break; + sprintf(buf, "\\x%02x", (unsigned char) c); + len = strlen(buf); + for (i = 0; i < len; i++) { + lns.ch = buf[i]; + process_char(&ctl, &lns); + } + } else + /* end of file */ + break; + } else + /* the common case */ + process_char(&ctl, &lns); + } + + /* goto the last line that had a character on it */ + for (; ctl.l->l_next; ctl.l = ctl.l->l_next) + lns.this_line++; + if (lns.max_line == 0 && lns.cur_col == 0) { +#ifdef COL_DEALLOCATE_ON_EXIT + free_line_allocations(ctl.alloc_root); +#endif + return EXIT_SUCCESS; /* no lines, so just exit */ + } + flush_lines(&ctl, lns.this_line - lns.nflushd_lines + lns.extra_lines + 1); + + /* make sure we leave things in a sane state */ + if (ctl.last_set != CS_NORMAL) + col_putchar(SI); + + /* flush out the last few blank lines */ + ctl.nblank_lines = lns.max_line - lns.this_line; + if (lns.max_line & 1) + ctl.nblank_lines++; + else if (!ctl.nblank_lines) + /* missing a \n on the last line? */ + ctl.nblank_lines = 2; + flush_blanks(&ctl); +#ifdef COL_DEALLOCATE_ON_EXIT + free_line_allocations(ctl.alloc_root); +#endif + return ret; +} diff --git a/text-utils/colcrt.1 b/text-utils/colcrt.1 new file mode 100644 index 0000000..869a91b --- /dev/null +++ b/text-utils/colcrt.1 @@ -0,0 +1,97 @@ +'\" t +.\" Title: colcrt +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "COLCRT" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +colcrt \- filter nroff output for CRT previewing +.SH "SYNOPSIS" +.sp +\fBcolcrt\fP [options] [\fIfile\fP ...] +.SH "DESCRIPTION" +.sp +\fBcolcrt\fP 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 `\-\*(Aq) are placed on new lines in between the normal output lines. +.SH "OPTIONS" +.sp +\fB\-\fP, \fB\-\-no\-underlining\fP +.RS 4 +Suppress all underlining. This option is especially useful for previewing \fIallboxed\fP tables from \fBtbl\fP(1). +.RE +.sp +\fB\-2\fP, \fB\-\-half\-lines\fP +.RS 4 +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 \fB\-2\fP option is useful for sending output to the line printer when the output contains superscripts and subscripts which would otherwise be partially invisible. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "HISTORY" +.sp +The \fBcolcrt\fP command appeared in 3.0BSD. +.SH "BUGS" +.sp +Should fold underlines onto blanks even with the \fB\-\fP option so that a true underline character would show. +.sp +Can\(cqt back up more than 102 lines. +.sp +General overstriking is lost; as a special case \*(Aq|\*(Aq overstruck with \*(Aq\-\*(Aq or underline becomes \*(Aq+\*(Aq. +.sp +Lines are trimmed to 132 characters. +.sp +Some provision should be made for processing superscripts and subscripts in documents which are already double\-spaced. +.SH "EXAMPLES" +.sp +A typical use of \fBcolcrt\fP would be: +.RS 3 +.ll -.6i +.sp +\fBtbl exum2.n | nroff \-ms | colcrt \- | more\fP +.br +.RE +.ll +.SH "SEE ALSO" +.sp +\fBcol\fP(1), +\fBmore\fP(1), +\fBnroff\fP(1), +\fBtroff\fP(1), +\fBul\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBcolcrt\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/colcrt.1.adoc b/text-utils/colcrt.1.adoc new file mode 100644 index 0000000..dee3155 --- /dev/null +++ b/text-utils/colcrt.1.adoc @@ -0,0 +1,106 @@ +//po4a: entry man manual +//// +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 +//// += colcrt(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: colcrt +:plus: + + +== NAME + +colcrt - filter nroff output for CRT previewing + +== SYNOPSIS + +*colcrt* [options] [_file_ ...] + +== DESCRIPTION + +*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. + +== OPTIONS + +*-*, *--no-underlining*:: +Suppress all underlining. This option is especially useful for previewing _allboxed_ tables from *tbl*(1). + +*-2*, *--half-lines*:: +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 *-2* option is useful for sending output to the line printer when the output contains superscripts and subscripts which would otherwise be partially invisible. + +include::man-common/help-version.adoc[] + +== HISTORY + +The *colcrt* command appeared in 3.0BSD. + +== BUGS + +Should fold underlines onto blanks even with the *-* option so that a true underline character would show. + +Can't back up more than 102 lines. + +//TRANSLATORS: Keep {plus} untranslated. +General overstriking is lost; as a special case '|' overstruck with '-' or underline becomes '{plus}'. + +Lines are trimmed to 132 characters. + +Some provision should be made for processing superscripts and subscripts in documents which are already double-spaced. + +== EXAMPLES + +A typical use of *colcrt* would be: + +____ +*tbl exum2.n | nroff -ms | colcrt - | more* +____ + + +== SEE ALSO + +*col*(1), +*more*(1), +*nroff*(1), +*troff*(1), +*ul*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] 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..54088ae --- /dev/null +++ b/text-utils/colrm.1 @@ -0,0 +1,69 @@ +'\" t +.\" Title: colrm +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "COLRM" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +colrm \- remove columns from a file +.SH "SYNOPSIS" +.sp +\fBcolrm\fP \fI[first [last]]\fP +.SH "DESCRIPTION" +.sp +\fBcolrm\fP removes selected columns from a file. Input is taken from standard input. Output is sent to standard output. +.sp +If called with one parameter the columns of each line will be removed starting with the specified \fIfirst\fP column. If called with two parameters the columns from the \fIfirst\fP column to the \fIlast\fP column will be removed. +.sp +Column numbering starts with column 1. +.SH "OPTIONS" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "HISTORY" +.sp +The \fBcolrm\fP command appeared in 3.0BSD. +.SH "SEE ALSO" +.sp +\fBawk\fP(1p), +\fBcolumn\fP(1), +\fBexpand\fP(1), +\fBpaste\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBcolrm\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/colrm.1.adoc b/text-utils/colrm.1.adoc new file mode 100644 index 0000000..5291339 --- /dev/null +++ b/text-utils/colrm.1.adoc @@ -0,0 +1,80 @@ +//po4a: entry man manual +//// +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 +//// += colrm(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: colrm + +== NAME + +colrm - remove columns from a file + +== SYNOPSIS + +*colrm* _[first [last]]_ + +== DESCRIPTION + +*colrm* removes selected columns from a file. Input is taken from standard input. Output is sent to standard output. + +If called with one parameter the columns of each line will be removed starting with the specified _first_ column. If called with two parameters the columns from the _first_ column to the _last_ column will be removed. + +Column numbering starts with column 1. + +== OPTIONS + +include::man-common/help-version.adoc[] + +== HISTORY + +The *colrm* command appeared in 3.0BSD. + +== SEE ALSO + +*awk*(1p), +*column*(1), +*expand*(1), +*paste*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/colrm.c b/text-utils/colrm.c new file mode 100644 index 0000000..7df5a74 --- /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, "Vh", 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..2ebeb5a --- /dev/null +++ b/text-utils/column.1 @@ -0,0 +1,335 @@ +'\" t +.\" Title: column +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "COLUMN" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +column \- columnate lists +.SH "SYNOPSIS" +.sp +\fBcolumn\fP [options] [\fIfile\fP ...] +.SH "DESCRIPTION" +.sp +The \fBcolumn\fP utility formats its input into multiple columns. The util support three modes: +.sp +\fBcolumns are filled before rows\fP +.RS 4 +This is the default mode (required by backward compatibility). +.RE +.sp +\fBrows are filled before columns\fP +.RS 4 +This mode is enabled by option \fB\-x, \-\-fillrows\fP +.RE +.sp +\fBtable\fP +.RS 4 +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. The output is aligned to the terminal width in interactive mode and the 80 columns in non\-interactive mode (see \fB\-\-output\-width\fP for more details). +.RE +.sp +Input is taken from \fIfile\fP, or otherwise from standard input. Empty lines are ignored and all invalid multibyte sequences are encoded by x<hex> convention. +.SH "OPTIONS" +.sp +The argument \fIcolumns\fP for \fB\-\-table\-\fP* options is a comma separated list of the +column names as defined by \fB\-\-table\-columns\fP, or names defined by +\fB\-\-table\-column\fP or it\(cqs column number in order as specified by input. It\(cqs +possible to mix names and numbers. The special placeholder \*(Aq0\*(Aq (e.g. \-R0) may +be used to specify all columns and \*(Aq\-1\*(Aq (e.g. \-R \-1) to specify the last visible column. +It\(cqs possible to use ranges like \*(Aq1\-5\*(Aq when addressing columns by numbers. +.sp +\fB\-J, \-\-json\fP +.RS 4 +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. +.RE +.sp +\fB\-c, \-\-output\-width\fP \fIwidth\fP +.RS 4 +Output is formatted to a width specified as number of characters. The original name of this option is \fB\-\-columns\fP; this name is deprecated since v2.30. Note that input longer than \fIwidth\fP is not truncated by default. The default is a terminal width and the 80 columns in non\-interactive mode. The column headers are never truncated. +.sp +The placeholder "unlimited" (or 0) is possible to use to not restrict output width. This is recommended for example when output to the files rather than on terminal. +.RE +.sp +\fB\-d, \-\-table\-noheadings\fP +.RS 4 +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. +.RE +.sp +\fB\-o, \-\-output\-separator\fP \fIstring\fP +.RS 4 +Specify the columns delimiter for table output (default is two spaces). +.RE +.sp +\fB\-s, \-\-separator\fP \fIseparators\fP +.RS 4 +Specify the possible input item delimiters (default is whitespace). +.RE +.sp +\fB\-t, \-\-table\fP +.RS 4 +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. +.RE +.sp +\fB\-C, \-\-table\-column\fP \fIproperties\fP +.RS 4 +Define one column by comma separated list of column attributes. +This option can be used more than once, every use defines just one column. +The properties replace some of \fB\-\-table\-\fP options. For example \fB\-\-table\-column +name=FOO,right\fP define one column where text is aligned to right. The option is +mutually exclusive to \fB\-\-table\-columns\fP. +.sp +The currently supported attributes are: +.sp +\fBname=string\fP +.RS 4 +Specifies column name. +.RE +.sp +\fBtrunc\fP +.RS 4 +The column text can be truncated when necessary. The same as \fB\-\-table\-truncate\fP. +.RE +.sp +\fBright\fP +.RS 4 +Right align text in the specified columns. The same as \fB\-\-table\-right\fP. +.RE +.sp +\fBwidth=number\fP +.RS 4 +Specifies column width. The width is used as a hint only. The width is strictly followed +only when \fBstrictwidth\fP attribute is used too. +.RE +.sp +\fBstrictwidth\fP +.RS 4 +Strictly follow column \fBwidth=\fP setting. +.RE +.sp +\fBnoextreme\fP +.RS 4 +Specify columns where is possible to ignore unusually long cells. See \fB\-\-table\-noextreme\fP for more details. +.RE +.sp +\fBwrap\fP +.RS 4 +Specify columns where is possible to use multi\-line cell for long text when necessary. See \fB\-\-table\-wrap\fP. +.RE +.sp +\fBhide\fP +.RS 4 +Don\(cqt print specified columns. See \fB\-\-table\-hide\fP. +.RE +.sp +\fBjson=type\fP +.RS 4 +Define column type for JSON output, Supported are string, number and boolean. +.RE +.RE +.sp +\fB\-N, \-\-table\-columns\fP \fInames\fP +.RS 4 +Specify the columns names by comma separated list of names. The names are used +for the table header or to address column in option argument. See also \fB\-\-table\-column\fP. +.RE +.sp +\fB\-l, \-\-table\-columns\-limit\fP \fInumber\fP +.RS 4 +Specify maximal number of the input columns. The last column will contain all remaining line data if the limit is smaller than the number of the columns in the input data. +.RE +.sp +\fB\-R, \-\-table\-right\fP \fIcolumns\fP +.RS 4 +Right align text in the specified columns. +.RE +.sp +\fB\-T, \-\-table\-truncate\fP \fIcolumns\fP +.RS 4 +Specify columns where text can be truncated when necessary, otherwise very long table entries may be printed on multiple lines. +.RE +.sp +\fB\-E, \-\-table\-noextreme\fP \fIcolumns\fP +.RS 4 +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. +.sp +The option is used for the last visible column by default. +.RE +.sp +\fB\-e, \-\-table\-header\-repeat\fP +.RS 4 +Print header line for each page. +.RE +.sp +\fB\-W, \-\-table\-wrap\fP \fIcolumns\fP +.RS 4 +Specify columns where is possible to use multi\-line cell for long text when necessary. +.RE +.sp +\fB\-H, \-\-table\-hide\fP \fIcolumns\fP +.RS 4 +Don\(cqt print specified columns. The special placeholder \*(Aq\-\*(Aq may be used to hide all unnamed columns (see \fB\-\-table\-columns\fP). +.RE +.sp +\fB\-O, \-\-table\-order\fP \fIcolumns\fP +.RS 4 +Specify columns order on output. +.RE +.sp +\fB\-n, \-\-table\-name\fP \fIname\fP +.RS 4 +Specify the table name used for JSON output. The default is "table". +.RE +.sp +\fB\-m, \-\-table\-maxout\fP +.RS 4 +Fill all available space on output. +.RE +.sp +\fB\-L, \-\-keep\-empty\-lines\fP +.RS 4 +Preserve whitespace\-only lines in the input. The default is ignore empty lines at all. This option\(cqs original name was \fB\-\-table\-empty\-lines\fP but is now deprecated because it gives the false impression that the option only applies to table mode. +.RE +.sp +\fB\-r, \-\-tree\fP \fIcolumn\fP +.RS 4 +Specify column to use tree\-like output. Note that the circular dependencies and other anomalies in child and parent relation are silently ignored. +.RE +.sp +\fB\-i, \-\-tree\-id\fP \fIcolumn\fP +.RS 4 +Specify column with line ID to create child\-parent relation. +.RE +.sp +\fB\-p, \-\-tree\-parent\fP \fIcolumn\fP +.RS 4 +Specify column with parent ID to create child\-parent relation. +.RE +.sp +\fB\-x, \-\-fillrows\fP +.RS 4 +Fill rows before filling columns. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "ENVIRONMENT" +.sp +The environment variable \fBCOLUMNS\fP is used to determine the size of the screen if no other information is available. +.SH "HISTORY" +.sp +The \fBcolumn\fP command appeared in 4.3BSD\-Reno. +.SH "BUGS" +.sp +Version 2.23 changed the \fB\-s\fP option to be non\-greedy, for example: +.sp +.if n .RS 4 +.nf +.fam C +printf "a:b:c\(rsn1::3\(rsn" | column \-t \-s \*(Aq:\*(Aq +.fam +.fi +.if n .RE +.sp +Old output: +.sp +.if n .RS 4 +.nf +.fam C +a b c +1 3 +.fam +.fi +.if n .RE +.sp +New output (since util\-linux 2.23): +.sp +.if n .RS 4 +.nf +.fam C +a b c +1 3 +.fam +.fi +.if n .RE +.sp +Historical versions of this tool indicated that "rows are filled before columns" by default, and that the \fB\-x\fP option reverses this. This wording did not reflect the actual behavior, and it has since been corrected (see above). Other implementations of \fBcolumn\fP may continue to use the older documentation, but the behavior should be identical in any case. +.SH "EXAMPLES" +.sp +Print fstab with header line and align number to the right: +.sp +.if n .RS 4 +.nf +.fam C +sed \*(Aqs/#.*//\*(Aq /etc/fstab | column \-\-table \-\-table\-columns SOURCE,TARGET,TYPE,OPTIONS,PASS,FREQ \-\-table\-right PASS,FREQ +.fam +.fi +.if n .RE +.sp +Print fstab and hide unnamed columns: +.sp +.if n .RS 4 +.nf +.fam C +sed \*(Aqs/#.*//\*(Aq /etc/fstab | column \-\-table \-\-table\-columns SOURCE,TARGET,TYPE \-\-table\-hide \- +.fam +.fi +.if n .RE +.sp +Print a tree: +.sp +.if n .RS 4 +.nf +.fam C +echo \-e \*(Aq1 0 A\(rsn2 1 AA\(rsn3 1 AB\(rsn4 2 AAA\(rsn5 2 AAB\*(Aq | column \-\-tree\-id 1 \-\-tree\-parent 2 \-\-tree 3 +1 0 A +2 1 |\-AA +4 2 | |\-AAA +5 2 | `\-AAB +3 1 `\-AB +.fam +.fi +.if n .RE +.SH "SEE ALSO" +.sp +\fBcolrm\fP(1), +\fBls\fP(1), +\fBpaste\fP(1), +\fBsort\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBcolumn\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/column.1.adoc b/text-utils/column.1.adoc new file mode 100644 index 0000000..17f6eb0 --- /dev/null +++ b/text-utils/column.1.adoc @@ -0,0 +1,247 @@ +//po4a: entry man manual +//// +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 +//// += column(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: column + +== NAME + +column - columnate lists + +== SYNOPSIS + +*column* [options] [_file_ ...] + +== DESCRIPTION + +The *column* utility formats its input into multiple columns. The util support three modes: + +*columns are filled before rows*:: +This is the default mode (required by backward compatibility). + +*rows are filled before columns*:: +This mode is enabled by option *-x, --fillrows* + +*table*:: +Determine the number of columns the input contains and create a table. This mode is enabled by option *-t, --table* and columns formatting is possible to modify by *--table-** options. Use this mode if not sure. The output is aligned to the terminal width in interactive mode and the 80 columns in non-interactive mode (see *--output-width* for more details). + +Input is taken from _file_, or otherwise from standard input. Empty lines are ignored and all invalid multibyte sequences are encoded by x<hex> convention. + +== OPTIONS + +The argument _columns_ for *--table-** options is a comma separated list of the +column names as defined by *--table-columns*, or names defined by +*--table-column* or it's column number in order as specified by input. It's +possible to mix names and numbers. The special placeholder '0' (e.g. -R0) may +be used to specify all columns and '-1' (e.g. -R -1) to specify the last visible column. +It's possible to use ranges like '1-5' when addressing columns by numbers. + +*-J, --json*:: +Use JSON output format to print the table, the option *--table-columns* is required and the option *--table-name* is recommended. + +*-c, --output-width* _width_:: +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 _width_ is not truncated by default. The default is a terminal width and the 80 columns in non-interactive mode. The column headers are never truncated. ++ +The placeholder "unlimited" (or 0) is possible to use to not restrict output width. This is recommended for example when output to the files rather than on terminal. + +*-d, --table-noheadings*:: +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. + +*-o, --output-separator* _string_:: +Specify the columns delimiter for table output (default is two spaces). + +*-s, --separator* _separators_:: +Specify the possible input item delimiters (default is whitespace). + +*-t, --table*:: +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 *--output-separator* option. Table output is useful for pretty-printing. + +*-C, --table-column* _properties_:: +Define one column by comma separated list of column attributes. +This option can be used more than once, every use defines just one column. +The properties replace some of *--table-* options. For example *--table-column +name=FOO,right* define one column where text is aligned to right. The option is +mutually exclusive to *--table-columns*. ++ +The currently supported attributes are: ++ +*name=string*;; +Specifies column name. +*trunc*;; +The column text can be truncated when necessary. The same as *--table-truncate*. +*right*;; +Right align text in the specified columns. The same as *--table-right*. +*width=number*;; +Specifies column width. The width is used as a hint only. The width is strictly followed +only when *strictwidth* attribute is used too. +*strictwidth*;; +Strictly follow column *width=* setting. +*noextreme*;; +Specify columns where is possible to ignore unusually long cells. See *--table-noextreme* for more details. +*wrap*;; +Specify columns where is possible to use multi-line cell for long text when necessary. See *--table-wrap*. +*hide*;; +Don't print specified columns. See *--table-hide*. +*json=type*;; +Define column type for JSON output, Supported are string, number and boolean. + +*-N, --table-columns* _names_:: +Specify the columns names by comma separated list of names. The names are used +for the table header or to address column in option argument. See also *--table-column*. + +*-l, --table-columns-limit* _number_:: +Specify maximal number of the input columns. The last column will contain all remaining line data if the limit is smaller than the number of the columns in the input data. + +*-R, --table-right* _columns_:: +Right align text in the specified columns. + +*-T, --table-truncate* _columns_:: +Specify columns where text can be truncated when necessary, otherwise very long table entries may be printed on multiple lines. + +*-E, --table-noextreme* _columns_:: +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. + +*-e, --table-header-repeat*:: +Print header line for each page. + +*-W, --table-wrap* _columns_:: +Specify columns where is possible to use multi-line cell for long text when necessary. + +*-H, --table-hide* _columns_:: +Don't print specified columns. The special placeholder '-' may be used to hide all unnamed columns (see *--table-columns*). + +*-O, --table-order* _columns_:: +Specify columns order on output. + +*-n, --table-name* _name_:: +Specify the table name used for JSON output. The default is "table". + +*-m, --table-maxout*:: +Fill all available space on output. + +*-L, --keep-empty-lines*:: +Preserve whitespace-only lines in the input. The default is ignore empty lines at all. This option's original name was *--table-empty-lines* but is now deprecated because it gives the false impression that the option only applies to table mode. + +*-r, --tree* _column_:: +Specify column to use tree-like output. Note that the circular dependencies and other anomalies in child and parent relation are silently ignored. + +*-i, --tree-id* _column_:: +Specify column with line ID to create child-parent relation. + +*-p, --tree-parent* _column_:: +Specify column with parent ID to create child-parent relation. + +*-x, --fillrows*:: +Fill rows before filling columns. + +include::man-common/help-version.adoc[] + +== ENVIRONMENT + +The environment variable *COLUMNS* is used to determine the size of the screen if no other information is available. + +== HISTORY + +The *column* command appeared in 4.3BSD-Reno. + +== BUGS + +Version 2.23 changed the *-s* option to be non-greedy, for example: + +.... +printf "a:b:c\n1::3\n" | column -t -s ':' +.... + +Old output: + +.... +a b c +1 3 +.... + +New output (since util-linux 2.23): + +.... +a b c +1 3 +.... + +Historical versions of this tool indicated that "rows are filled before columns" by default, and that the *-x* option reverses this. This wording did not reflect the actual behavior, and it has since been corrected (see above). Other implementations of *column* may continue to use the older documentation, but the behavior should be identical in any case. + +== EXAMPLES + +Print fstab with header line and align number to the right: + +.... +sed 's/#.*//' /etc/fstab | column --table --table-columns SOURCE,TARGET,TYPE,OPTIONS,PASS,FREQ --table-right PASS,FREQ +.... + +Print fstab and hide unnamed columns: + +.... +sed 's/#.*//' /etc/fstab | column --table --table-columns SOURCE,TARGET,TYPE --table-hide - +.... + +Print a tree: + +.... +echo -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 +1 0 A +2 1 |-AA +4 2 | |-AAA +5 2 | `-AAB +3 1 `-AB +.... + +== SEE ALSO + +*colrm*(1), +*ls*(1), +*paste*(1), +*sort*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/column.c b/text-utils/column.c new file mode 100644 index 0000000..0f2e7e1 --- /dev/null +++ b/text-utils/column.c @@ -0,0 +1,1009 @@ +/* + * 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; /* -1 uninilialized, 0 unlimited, >0 width (default is 80) */ + + struct libscols_table *tab; + + char **tab_colnames; /* array with column names */ + const char *tab_name; /* table name */ + const char *tab_order; /* --table-order */ + + char **tab_columns; /* array from --table-column */ + + 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) */ + size_t maxncols; /* maximal number of input columns */ + + unsigned int greedy :1, + json :1, + header_repeat :1, + hide_unnamed :1, + maxout : 1, + keep_empty_lines :1, /* --keep-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(); + if (errmsg) + errx(EXIT_FAILURE, "%s: '%s'", errmsg, str); + else + return NULL; + } + 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); + + scols_table_enable_maxout(ctl->tab, ctl->maxout ? 1 : 0); + + if (ctl->tab_columns) { + char **opts; + + STRV_FOREACH(opts, ctl->tab_columns) { + struct libscols_column *cl; + + cl = scols_table_new_column(ctl->tab, NULL, 0, 0); + scols_column_set_properties(cl, *opts); + } + + } else if (ctl->tab_colnames) { + char **name; + + STRV_FOREACH(name, ctl->tab_colnames) + scols_table_new_column(ctl->tab, *name, 0, 0); + } else + scols_table_enable_noheadings(ctl->tab, 1); + + if (ctl->tab_colnames || ctl->tab_columns) { + if (ctl->header_repeat) + scols_table_enable_header_repeat(ctl->tab, 1); + scols_table_enable_noheadings(ctl->tab, !!ctl->tab_noheadings); + } + +} + +static struct libscols_column *get_last_visible_column(struct column_control *ctl, int n) +{ + struct libscols_iter *itr; + struct libscols_column *cl, *res = NULL; + + itr = scols_new_iter(SCOLS_ITER_BACKWARD); + if (!itr) + err_oom(); + + while (scols_table_next_column(ctl->tab, itr, &cl) == 0) { + if (scols_column_get_flags(cl) & SCOLS_FL_HIDDEN) + continue; + if (n == 0) { + res = cl; + break; + } + n--; + } + + scols_free_iter(itr); + return res; +} + +static struct libscols_column *string_to_column(struct column_control *ctl, const char *str) +{ + struct libscols_column *cl; + + if (isdigit_string(str)) { + uint32_t n = strtou32_or_err(str, _("failed to parse column")) - 1; + + cl = scols_table_get_column(ctl->tab, n); + } else if (strcmp(str, "-1") == 0) + cl = get_last_visible_column(ctl, 0); + else + cl = scols_table_get_column_by_name(ctl->tab, str); + + if (!cl) + errx(EXIT_FAILURE, _("undefined column name '%s'"), str); + + return cl; +} + +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 int has_unnamed(const char *list) +{ + char **all, **one; + int rc = 0; + + if (!list) + return 0; + if (strcmp(list, "-") == 0) + return 1; + if (!strchr(list, ',')) + return 0; + + all = split_or_error(list, NULL); + if (all) { + STRV_FOREACH(one, all) { + if (strcmp(*one, "-") == 0) { + rc = 1; + break; + } + } + strv_free(all); + } + + return rc; +} + +static void apply_columnflag_from_list(struct column_control *ctl, const char *list, + int flag, const char *errmsg) +{ + char **all; + char **one; + int unnamed = 0; + struct libscols_column *cl; + + /* apply to all */ + if (list && strcmp(list, "0") == 0) { + struct libscols_iter *itr; + + itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!itr) + err_oom(); + + while (scols_table_next_column(ctl->tab, itr, &cl) == 0) + column_set_flag(cl, flag); + scols_free_iter(itr); + return; + } + + all = split_or_error(list, errmsg); + + /* apply to columns specified by name */ + STRV_FOREACH(one, all) { + int low = 0, up = 0; + + if (strcmp(*one, "-") == 0) { + unnamed = 1; + continue; + } + + /* parse range (N-M) */ + if (strchr(*one, '-') && parse_range(*one, &low, &up, 0) == 0) { + for (; low <= up; low++) { + if (low < 0) + cl = get_last_visible_column(ctl, (low * -1) -1); + else + cl = scols_table_get_column(ctl->tab, low-1); + if (cl) + column_set_flag(cl, flag); + } + continue; + } + + /* one item in the list */ + 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; + + 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_name(cl)) + 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) +{ + if (ctl->termwidth > 0) { + scols_table_set_termwidth(ctl->tab, ctl->termwidth); + scols_table_set_termforce(ctl->tab, SCOLS_TERMFORCE_ALWAYS); + } + + if (ctl->tab_colhide) + apply_columnflag_from_list(ctl, ctl->tab_colhide, + SCOLS_FL_HIDDEN , _("failed to parse --table-hide list")); + + 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_colnoextrem) { + struct libscols_column *cl = get_last_visible_column(ctl, 0); + 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 *wcs0) +{ + wchar_t *sv = NULL, *wcs = wcs0, *all = NULL; + size_t n = 0; + struct libscols_line *ln = NULL; + + + if (!ctl->tab) + init_table(ctl); + + if (ctl->maxncols) { + all = wcsdup(wcs0); + if (!all) + err(EXIT_FAILURE, _("failed to allocate input line")); + } + + do { + char *data; + wchar_t *wcdata = local_wcstok(ctl, wcs, &sv); + + if (!wcdata) + break; + + if (ctl->maxncols && n + 1 == ctl->maxncols) { + /* Use rest of the string as column data */ + size_t skip = wcdata - wcs0; + wcdata = all + skip; + } + + if (scols_table_get_ncols(ctl->tab) < n + 1) { + if (scols_table_is_json(ctl->tab) && !ctl->hide_unnamed) + 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, + ctl->hide_unnamed ? SCOLS_FL_HIDDEN : 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; + if (ctl->maxncols && n == ctl->maxncols) + break; + } while (1); + + free(all); + 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 void add_entry(struct column_control *ctl, size_t *maxents, wchar_t *wcs) +{ + if (ctl->nents <= *maxents) { + *maxents += 1000; + ctl->ents = xrealloc(ctl->ents, *maxents * sizeof(wchar_t *)); + } + ctl->ents[ctl->nents] = wcs; + ctl->nents++; +} + +static int read_input(struct column_control *ctl, FILE *fp) +{ + wchar_t *empty = NULL; + 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->keep_empty_lines) { + if (ctl->mode == COLUMN_MODE_TABLE) { + add_emptyline_to_table(ctl); + } else { + if (!empty) + empty = mbs_to_wcs(""); + add_entry(ctl, &maxents, empty); + } + } + 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: + add_entry(ctl, &maxents, wcs); + len = width(wcs); + if (ctl->maxlength < len) + ctl->maxlength = len; + 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(_(" -C, --table-column <properties> define column\n"), out); + fputs(_(" -N, --table-columns <names> comma separated columns names\n"), out); + fputs(_(" -l, --table-columns-limit <num> maximal number of input columns\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(_(" -m, --table-maxout fill all available space\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, --keep-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' }, + { "keep-empty-lines", no_argument, NULL, 'L' }, + { "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-column", required_argument, NULL, 'C' }, + { "table-columns-limit", required_argument, NULL, 'l' }, + { "table-hide", required_argument, NULL, 'H' }, + { "table-name", required_argument, NULL, 'n' }, + { "table-maxout", no_argument, NULL, 'm' }, + { "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' }, /* deprecated */ + { "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 */ + { 'C','N' }, + { '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:c:dE:eH:hi:Jl:LN:n:mO:o:p:R:r:s:T:tVW:x", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'C': + if (strv_extend(&ctl.tab_columns, optarg)) + err_oom(); + break; + case 'c': + if (strcmp(optarg, "unlimited") == 0) + ctl.termwidth = 0; + else + 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; + ctl.hide_unnamed = has_unnamed(ctl.tab_colhide); + break; + case 'i': + ctl.tree_id = optarg; + break; + case 'J': + ctl.json = 1; + ctl.mode = COLUMN_MODE_TABLE; + break; + case 'L': + ctl.keep_empty_lines = 1; + break; + case 'l': + ctl.maxncols = strtou32_or_err(optarg, _("invalid columns limit argument")); + if (ctl.maxncols == 0) + errx(EXIT_FAILURE, _("columns limit must be greater than zero")); + break; + case 'N': + ctl.tab_colnames = split_or_error(optarg, _("failed to parse column names")); + break; + case 'n': + ctl.tab_name = optarg; + break; + case 'm': + ctl.maxout = 1; + 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); + if (!ctl.input_separator) + err(EXIT_FAILURE, _("failed to use input separator")); + 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 || ctl.tab_columns)) + errx(EXIT_FAILURE, _("option --table required for all --table-*")); + + if (!ctl.tab_colnames && !ctl.tab_columns && ctl.json) + errx(EXIT_FAILURE, _("option --table-columns or --table-column 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); + + scols_unref_table(ctl.tab); + if (ctl.tab_colnames) + strv_free(ctl.tab_colnames); + if (ctl.tab_columns) + strv_free(ctl.tab_columns); + } + 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; + } + + free(ctl.input_separator); + + 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..bc92bd0 --- /dev/null +++ b/text-utils/hexdump-display.c @@ -0,0 +1,456 @@ +/* + * 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: + { + char cval; /* int8_t */ + short sval; /* int16_t */ + int ival; /* int32_t */ + long long Lval; /* int64_t, int64_t */ + + switch(pr->bcnt) { + case 1: + memmove(&cval, bp, sizeof(cval)); + printf(pr->fmt, (unsigned long long) cval); + 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..272bb24 --- /dev/null +++ b/text-utils/hexdump-parse.c @@ -0,0 +1,651 @@ +/* + * 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 = 0; + + 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); +} + +static char *next_number(const char *str, int *num) +{ + char *end = NULL; + + errno = 0; + *num = strtol(str, &end, 10); + + if (errno || !end || end == str) + return NULL; + return end; +} + +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)) { + p = next_number(p, &tfu->reps); + if (!p || (!isspace(*p) && *p != '/')) + badfmt(fmt); + + /* may overwrite either white space or slash */ + 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)) { + p = next_number(p, &tfu->bcnt); + if (!p || !isspace(*p)) + badfmt(fmt); + /* 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) && *fmt != '\0') + ; + if (*fmt == '.' && isdigit(*++fmt)) + fmt = next_number(fmt, &prec); + if (*fmt == '\0') + badfmt(fu->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; + p1 = next_number(p1, &prec); + } 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..3dc84a3 --- /dev/null +++ b/text-utils/hexdump.1 @@ -0,0 +1,498 @@ +'\" t +.\" Title: hexdump +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "HEXDUMP" "1" "2023-12-01" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +hexdump \- display file contents in hexadecimal, decimal, octal, or ascii +.sp +\fBhexdump\fP \fIoptions file\fP ... +.sp +\fBhd\fP \fIoptions file\fP ... +.SH "DESCRIPTION" +.sp +The \fBhexdump\fP utility is a filter which displays the specified files, or standard input if no files are specified, in a user\-specified format. +.SH "OPTIONS" +.sp +Below, the \fIlength\fP and \fIoffset\fP 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. +.sp +\fB\-b\fP, \fB\-\-one\-byte\-octal\fP +.RS 4 +\fIOne\-byte octal display\fP. Display the input offset in hexadecimal, followed by sixteen space\-separated, three\-column, zero\-filled bytes of input data, in octal, per line. +.RE +.sp +\fB\-c\fP, \fB\-\-one\-byte\-char\fP +.RS 4 +\fIOne\-byte character display\fP. Display the input offset in hexadecimal, followed by sixteen space\-separated, three\-column, space\-filled characters of input data per line. +.RE +.sp +\fB\-C\fP, \fB\-\-canonical\fP +.RS 4 +\fICanonical hex+ASCII display\fP. Display the input offset in hexadecimal, followed by sixteen space\-separated, two\-column, hexadecimal bytes, followed by the same sixteen bytes in \fB%_p\fP format enclosed in \fB|\fP characters. Invoking the program as \fBhd\fP implies this option. +.RE +.sp +\fB\-d\fP, \fB\-\-two\-bytes\-decimal\fP +.RS 4 +\fITwo\-byte decimal display\fP. 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. +.RE +.sp +\fB\-e\fP, \fB\-\-format\fP \fIformat_string\fP +.RS 4 +Specify a format string to be used for displaying data. +.RE +.sp +\fB\-f\fP, \fB\-\-format\-file\fP \fIfile\fP +.RS 4 +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. +.RE +.sp +\fB\-L\fP, \fB\-\-color\fP[=\fIwhen\fP] +.RS 4 +Accept color units for the output. The optional argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. If the \fIwhen\fP argument is omitted, it defaults to \fBauto\fP. The colors can be disabled; for the current built\-in default see the \fB\-\-help\fP output. See also the \fBColors\fP subsection and the \fBCOLORS\fP section below. +.RE +.sp +\fB\-n\fP, \fB\-\-length\fP \fIlength\fP +.RS 4 +Interpret only \fIlength\fP bytes of input. +.RE +.sp +\fB\-o\fP, \fB\-\-two\-bytes\-octal\fP +.RS 4 +\fITwo\-byte octal display\fP. 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. +.RE +.sp +\fB\-s\fP, \fB\-\-skip\fP \fIoffset\fP +.RS 4 +Skip \fIoffset\fP bytes from the beginning of the input. +.RE +.sp +\fB\-v\fP, \fB\-\-no\-squeezing\fP +.RS 4 +The \fB\-v\fP option causes \fBhexdump\fP to display all input data. Without the \fB\-v\fP 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. +.RE +.sp +\fB\-x\fP, \fB\-\-two\-bytes\-hex\fP +.RS 4 +\fITwo\-byte hexadecimal display\fP. 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. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.sp +For each input file, \fBhexdump\fP sequentially copies the input to standard output, transforming the data according to the format strings specified by the \fB\-e\fP and \fB\-f\fP options, in the order that they were specified. +.SH "FORMATS" +.sp +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. +.sp +The iteration count is an optional positive integer, which defaults to one. Each format is applied iteration count times. +.sp +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. +.sp +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. +.sp +The format is required and must be surrounded by double quote (" ") marks. It is interpreted as a fprintf\-style format string (see \fBfprintf\fP(3)), with the following exceptions: +.sp +1. +.RS 4 +An asterisk (*) may not be used as a field width or precision. +.RE +.sp +2. +.RS 4 +A byte count or field precision \fIis\fP required for each \fBs\fP conversion character (unlike the \fBfprintf\fP(3) default which prints the entire string if the precision is unspecified). +.RE +.sp +3. +.RS 4 +The conversion characters \fBh\fP, \fBl\fP, \fBn\fP, \fBp\fP, and \fBq\fP are not supported. +.RE +.sp +4. +.RS 4 +The single character escape sequences described in the C standard are supported: +.RE +.RS 3 +.ll -.6i +.TS +allbox tab(:); +lt lt. +T{ +.sp +NULL +T}:T{ +.sp +\(rs0 +T} +T{ +.sp +<alert character> +T}:T{ +.sp +\(rsa +T} +T{ +.sp +<backspace> +T}:T{ +.sp +\(rsb +T} +T{ +.sp +<form\-feed> +T}:T{ +.sp +\(rsf +T} +T{ +.sp +<newline> +T}:T{ +.sp +\(rsn +T} +T{ +.sp +<carriage return> +T}:T{ +.sp +\(rsr +T} +T{ +.sp +<tab> +T}:T{ +.sp +\(rst +T} +T{ +.sp +<vertical tab> +T}:T{ +.sp +\(rsv +T} +.TE +.sp +.br +.RE +.ll +.SS "Conversion strings" +.sp +The \fBhexdump\fP utility also supports the following additional conversion strings. +.sp +\fB_a[dox]\fP +.RS 4 +Display the input offset, cumulative across input files, of the next byte to be displayed. The appended characters \fBd\fP, \fBo\fP, and \fBx\fP specify the display base as decimal, octal or hexadecimal respectively. +.RE +.sp +\fB_A[dox]\fP +.RS 4 +Almost identical to the \fB_a\fP conversion string except that it is only performed once, when all of the input data has been processed. +.RE +.sp +\fB_c\fP +.RS 4 +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. +.RE +.sp +\fB_p\fP +.RS 4 +Output characters in the default character set. Non\-printing characters are displayed as a single \*(Aq\fB.\fP\*(Aq. +.RE +.sp +\fB_u\fP +.RS 4 +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. +.RE +.RS 3 +.ll -.6i +.TS +allbox tab(:); +lt lt lt lt lt lt. +T{ +.sp +000 nul +T}:T{ +.sp +001 soh +T}:T{ +.sp +002 stx +T}:T{ +.sp +003 etx +T}:T{ +.sp +004 eot +T}:T{ +.sp +005 enq +T} +T{ +.sp +006 ack +T}:T{ +.sp +007 bel +T}:T{ +.sp +008 bs +T}:T{ +.sp +009 ht +T}:T{ +.sp +00A lf +T}:T{ +.sp +00B vt +T} +T{ +.sp +00C ff +T}:T{ +.sp +00D cr +T}:T{ +.sp +00E so +T}:T{ +.sp +00F si +T}:T{ +.sp +010 dle +T}:T{ +.sp +011 dc1 +T} +T{ +.sp +012 dc2 +T}:T{ +.sp +013 dc3 +T}:T{ +.sp +014 dc4 +T}:T{ +.sp +015 nak +T}:T{ +.sp +016 syn +T}:T{ +.sp +017 etb +T} +T{ +.sp +018 can +T}:T{ +.sp +019 em +T}:T{ +.sp +01A sub +T}:T{ +.sp +01B esc +T}:T{ +.sp +01C fs +T}:T{ +.sp +01D gs +T} +T{ +.sp +01E rs +T}:T{ +.sp +01F us +T}:T{ +.sp +0FF del +T}:T{ +.sp + +T}:T{ +.sp + +T}:T{ +.sp + +T} +.TE +.sp +.br +.RE +.ll +.SS "Colors" +.sp +When put at the end of a format specifier, \fBhexdump\fP highlights the respective string with the color specified. Conditions, if present, are evaluated prior to highlighting. +.sp +\fB_L[color_unit_1,color_unit_2,...,color_unit_n]\fP +.sp +The full syntax of a color unit is as follows: +.sp +\fB[!]COLOR[:VALUE][@OFFSET_START[\-END]]\fP +.sp +\fB!\fP +.RS 4 +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. +.RE +.sp +\fBCOLOR\fP +.RS 4 +One of the 8 basic shell colors. +.RE +.sp +\fBVALUE\fP +.RS 4 +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 \fBhexdump\fP inside the color_units. +.RE +.sp +\fBOFFSET\fP +.RS 4 +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. +.RE +.SS "Counters" +.sp +The default and supported byte counts for the conversion characters are as follows: +.sp +\fB%_c\fP, \fB%_p\fP, \fB%_u\fP, \fB%c\fP +.RS 4 +One byte counts only. +.RE +.sp +\fB%d\fP, \fB%i\fP, \fB%o\fP, \fB%u\fP, \fB%X\fP, \fB%x\fP +.RS 4 +Four byte default, one, two and four byte counts supported. +.RE +.sp +\fB%E\fP, \fB%e\fP, \fB%f\fP, \fB%G\fP, \fB%g\fP +.RS 4 +Eight byte default, four byte counts supported. +.RE +.sp +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. +.sp +The input is manipulated in \fIblocks\fP, where a block is defined as the largest amount of data specified by any format string. Format strings interpreting less than an input block\(cqs 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. +.sp +If, either as a result of user specification or \fBhexdump\fP modifying the iteration count as described above, an iteration count is greater than one, no trailing whitespace characters are output during the last iteration. +.sp +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 \fB_a\fP or \fB_A\fP. +.sp +If, as a result of the specification of the \fB\-n\fP 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). +.sp +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 \fBs\fP conversion character with the same field width and precision as the original conversion character or conversion string but with any \*(Aq\fB+\fP\*(Aq, \*(Aq \*(Aq, \*(Aq\fB#\fP\*(Aq conversion flag characters removed, and referencing a NULL string. +.sp +If no format strings are specified, the default display is very similar to the \fB\-x\fP output format (the \fB\-x\fP option causes more space to be used between format units than in the default output). +.SH "EXIT STATUS" +.sp +\fBhexdump\fP exits 0 on success and > 0 if an error occurred. +.SH "CONFORMING TO" +.sp +The \fBhexdump\fP utility is expected to be IEEE Std 1003.2 ("POSIX.2") compatible. +.SH "EXAMPLES" +.sp +Display the input in perusal format: +.sp +.if n .RS 4 +.nf +.fam C + "%06.6_ao " 12/1 "%3_u " + "\(rst" "%_p " + "\(rsn" +.fam +.fi +.if n .RE +.sp +Implement the \fB\-x\fP option: +.sp +.if n .RS 4 +.nf +.fam C + "%07.7_Ax\(rsn" + "%07.7_ax " 8/2 "%04x " "\(rsn" +.fam +.fi +.if n .RE +.sp +MBR Boot Signature example: Highlight the addresses cyan and the bytes at offsets 510 and 511 green if their value is 0xAA55, red otherwise. +.sp +.if n .RS 4 +.nf +.fam C + "%07.7_Ax_L[cyan]\(rsn" + "%07.7_ax_L[cyan] " 8/2 " %04x_L[green:0xAA55@510\-511,!red:0xAA55@510\-511] " "\(rsn" +.fam +.fi +.if n .RE +.SH "COLORS" +.sp +The output colorization is implemented by \fBterminal\-colors.d\fP(5) functionality. +Implicit coloring can be disabled by an empty file +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/hexdump.disable\fP +.br +.RE +.ll +.sp +for the \fBhexdump\fP command or for all tools by +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/disable\fP +.br +.RE +.ll +.sp +The user\-specific \fI$XDG_CONFIG_HOME/terminal\-colors.d\fP +or \fI$HOME/.config/terminal\-colors.d\fP overrides the global setting. +.sp +Note that the output colorization may be enabled by default, and in this case +\fIterminal\-colors.d\fP directories do not have to exist yet. +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBhexdump\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/hexdump.1.adoc b/text-utils/hexdump.1.adoc new file mode 100644 index 0000000..82c1e60 --- /dev/null +++ b/text-utils/hexdump.1.adoc @@ -0,0 +1,259 @@ +//po4a: entry man manual +//// +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 +//// += hexdump(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: hexdump +:plus: + +:underscore: _ + +== NAME + +hexdump - display file contents in hexadecimal, decimal, octal, or ascii + +*hexdump* _options file_ ... + +*hd* _options file_ ... + +== DESCRIPTION + +The *hexdump* utility is a filter which displays the specified files, or standard input if no files are specified, in a user-specified format. + +== OPTIONS + +Below, the _length_ and _offset_ 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. + +*-b*, *--one-byte-octal*:: +_One-byte octal display_. Display the input offset in hexadecimal, followed by sixteen space-separated, three-column, zero-filled bytes of input data, in octal, per line. + +*-c*, *--one-byte-char*:: +_One-byte character display_. Display the input offset in hexadecimal, followed by sixteen space-separated, three-column, space-filled characters of input data per line. + +*-C*, *--canonical*:: +_Canonical hex{plus}ASCII display_. Display the input offset in hexadecimal, followed by sixteen space-separated, two-column, hexadecimal bytes, followed by the same sixteen bytes in *%{underscore}p* format enclosed in *|* characters. Invoking the program as *hd* implies this option. +//TRANSLATORS: Keep {plus} and {underscore} untranslated. + +*-d*, *--two-bytes-decimal*:: +_Two-byte decimal display_. 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. + +*-e*, *--format* _format_string_:: +Specify a format string to be used for displaying data. + +*-f*, *--format-file* _file_:: +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. + +*-L*, *--color*[=_when_]:: +Accept color units for the output. The optional argument _when_ can be *auto*, *never* or *always*. If the _when_ argument is omitted, it defaults to *auto*. The colors can be disabled; for the current built-in default see the *--help* output. See also the *Colors* subsection and the *COLORS* section below. + +*-n*, *--length* _length_:: +Interpret only _length_ bytes of input. + +*-o*, *--two-bytes-octal*:: +_Two-byte octal display_. 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. + +*-s*, *--skip* _offset_:: +Skip _offset_ bytes from the beginning of the input. + +*-v*, *--no-squeezing*:: +The *-v* option causes *hexdump* to display all input data. Without the *-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. + +*-x*, *--two-bytes-hex*:: +_Two-byte hexadecimal display_. 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. + +include::man-common/help-version.adoc[] + +For each input file, *hexdump* sequentially copies the input to standard output, transforming the data according to the format strings specified by the *-e* and *-f* options, in the order that they were specified. + +== 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. + +The iteration count is an optional positive integer, which defaults to one. Each format is applied iteration count times. + +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. + +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. + +The format is required and must be surrounded by double quote (" ") marks. It is interpreted as a fprintf-style format string (see *fprintf*(3)), with the following exceptions: + +1.:: +An asterisk (*) may not be used as a field width or precision. + +2.:: +A byte count or field precision _is_ required for each *s* conversion character (unlike the *fprintf*(3) default which prints the entire string if the precision is unspecified). + +3.:: +The conversion characters *h*, *l*, *n*, *p*, and *q* are not supported. + +4.:: +The single character escape sequences described in the C standard are supported: + +____ +|=== +|NULL |\0 +|<alert character> |\a +|<backspace> |\b +|<form-feed> |\f +|<newline> |\n +|<carriage return> |\r +|<tab> |\t +|<vertical tab> |\v +|=== +____ + +=== Conversion strings + +The *hexdump* utility also supports the following additional conversion strings. + +*_a[dox]*:: +Display the input offset, cumulative across input files, of the next byte to be displayed. The appended characters *d*, *o*, and *x* specify the display base as decimal, octal or hexadecimal respectively. + +*_A[dox]*:: +Almost identical to the *_a* conversion string except that it is only performed once, when all of the input data has been processed. + +*_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. + +*_p*:: +Output characters in the default character set. Non-printing characters are displayed as a single '*.*'. + +*_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. + +____ +|=== +|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 | | | +|=== +____ + +=== 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. + +*_L[color_unit_1,color_unit_2,...,color_unit_n]* + +The full syntax of a color unit is as follows: + +*[!]COLOR[:VALUE][@OFFSET_START[-END]]* + +*!*:: +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. + +*COLOR*:: +One of the 8 basic shell colors. + +*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. + +*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. + +=== Counters + +The default and supported byte counts for the conversion characters are as follows: + +*%_c*, *%_p*, *%_u*, *%c*:: +One byte counts only. + +*%d*, *%i*, *%o*, *%u*, *%X*, *%x*:: +Four byte default, one, two and four byte counts supported. + +*%E*, *%e*, *%f*, *%G*, *%g*:: +Eight byte default, four byte counts supported. + +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. + +The input is manipulated in _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. + +If, either as a result of user specification or *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. + +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 *_a* or *_A*. + +If, as a result of the specification of the *-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). + +//TRANSLATORS: Keep {plus} untranslated. +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 *s* conversion character with the same field width and precision as the original conversion character or conversion string but with any '*{plus}*', ' ', '*#*' conversion flag characters removed, and referencing a NULL string. + +If no format strings are specified, the default display is very similar to the *-x* output format (the *-x* option causes more space to be used between format units than in the default output). + +== EXIT STATUS + +*hexdump* exits 0 on success and > 0 if an error occurred. + +== CONFORMING TO + +The *hexdump* utility is expected to be IEEE Std 1003.2 ("POSIX.2") compatible. + +== EXAMPLES + +Display the input in perusal format: + +.... + "%06.6_ao " 12/1 "%3_u " + "\t" "%_p " + "\n" +.... + +Implement the *-x* option: + +.... + "%07.7_Ax\n" + "%07.7_ax " 8/2 "%04x " "\n" +.... + +MBR Boot Signature example: Highlight the addresses cyan and the bytes at offsets 510 and 511 green if their value is 0xAA55, red otherwise. + +.... + "%07.7_Ax_L[cyan]\n" + "%07.7_ax_L[cyan] " 8/2 " %04x_L[green:0xAA55@510-511,!red:0xAA55@510-511] " "\n" +.... + +include::man-common/colors.adoc[] + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/hexdump.c b/text-utils/hexdump.c new file mode 100644 index 0000000..73b3a94 --- /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} + }; + + 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)) { + 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); + } else { + 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..0ea18b2 --- /dev/null +++ b/text-utils/line.1 @@ -0,0 +1,48 @@ +'\" t +.\" Title: line +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "LINE" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +line \- read one line +.SH "SYNOPSIS" +.sp +\fBline\fP +.SH "DESCRIPTION" +.sp +The utility \fBline\fP 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" +.sp +\fBread\fP(1p) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBline\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/line.1.adoc b/text-utils/line.1.adoc new file mode 100644 index 0000000..d3c929b --- /dev/null +++ b/text-utils/line.1.adoc @@ -0,0 +1,33 @@ +//po4a: entry man manual +// This page is in the public domain += line(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: line + +== NAME + +line - read one line + +== SYNOPSIS + +*line* + +== DESCRIPTION + +The utility *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. + +== SEE ALSO + +*read*(1p) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] + 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/meson.build b/text-utils/meson.build new file mode 100644 index 0000000..f3b25d3 --- /dev/null +++ b/text-utils/meson.build @@ -0,0 +1,43 @@ +col_sources = files( + 'col.c', +) + +colcrt_sources = files( + 'colcrt.c', +) + +colrm_sources = files( + 'colrm.c', +) + +rev_sources = files( + 'rev.c', +) + +column_sources = files( + 'column.c', +) + +line_sources = files( + 'line.c', +) + +pg_sources = files( + 'pg.c', +) + +ul_sources = files( + 'ul.c', +) + +more_sources = files( + 'more.c', +) + +hexdump_sources = files( + 'hexdump.c', + 'hexdump.h', + 'hexdump-conv.c', + 'hexdump-display.c', + 'hexdump-parse.c', +) diff --git a/text-utils/more.1 b/text-utils/more.1 new file mode 100644 index 0000000..c5cd80e --- /dev/null +++ b/text-utils/more.1 @@ -0,0 +1,268 @@ +'\" t +.\" Title: more +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "MORE" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +more \- display the contents of a file in a terminal +.SH "SYNOPSIS" +.sp +\fBmore\fP [options] \fIfile\fP ... +.SH "DESCRIPTION" +.sp +\fBmore\fP is a filter for paging through text one screenful at a time. This version is especially primitive. Users should realize that \fBless\fP(1) provides \fBmore\fP(1) emulation plus extensive enhancements. +.SH "OPTIONS" +.sp +Options are also taken from the environment variable \fBMORE\fP (make sure to precede them with a dash (\fB\-\fP)) but command\-line options will override those. +.sp +\fB\-d\fP, \fB\-\-silent\fP +.RS 4 +Prompt with "[Press space to continue, \*(Aqq\*(Aq to quit.]", and display "[Press \*(Aqh\*(Aq for instructions.]" instead of ringing the bell when an illegal key is pressed. +.RE +.sp +\fB\-l\fP, \fB\-\-logical\fP +.RS 4 +Do not pause after any line containing a \fB^L\fP (form feed). +.RE +.sp +\fB\-e\fP, \fB\-\-exit\-on\-eof\fP +.RS 4 +Exit on End\-Of\-File, enabled by default if POSIXLY_CORRECT environment variable is not set or if not executed on terminal. +.RE +.sp +\fB\-f\fP, \fB\-\-no\-pause\fP +.RS 4 +Count logical lines, rather than screen lines (i.e., long lines are not folded). +.RE +.sp +\fB\-p\fP, \fB\-\-print\-over\fP +.RS 4 +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 \fBpage\fP. +.RE +.sp +\fB\-c\fP, \fB\-\-clean\-print\fP +.RS 4 +Do not scroll. Instead, paint each screen from the top, clearing the remainder of each line as it is displayed. +.RE +.sp +\fB\-s\fP, \fB\-\-squeeze\fP +.RS 4 +Squeeze multiple blank lines into one. +.RE +.sp +\fB\-u\fP, \fB\-\-plain\fP +.RS 4 +Suppress underlining. This option is silently ignored as backwards compatibility. +.RE +.sp +\fB\-n\fP, \fB\-\-lines\fP \fInumber\fP +.RS 4 +Specify the \fInumber\fP of lines per screenful. The \fInumber\fP argument is a positive decimal integer. The \fB\-\-lines\fP option shall override any values obtained from any other source, such as number of lines reported by terminal. +.RE +.sp +\fB\-\fP\fInumber\fP +.RS 4 +A numeric option means the same as \fB\-\-lines\fP option argument. +.RE +.sp +\fB+\fP\fInumber\fP +.RS 4 +Start displaying each file at line \fInumber\fP. +.RE +.sp +\fB+\fP/\fIstring\fP +.RS 4 +The \fIstring\fP to be searched in each file before starting to display it. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "COMMANDS" +.sp +Interactive commands for \fBmore\fP are based on \fBvi\fP(1). Some commands may be preceded by a decimal number, called k in the descriptions below. In the following descriptions, \fB^X\fP means \fBcontrol\-X\fP. +.sp +\fBh\fP or \fB?\fP +.RS 4 +Help; display a summary of these commands. If you forget all other commands, remember this one. +.RE +.sp +\fBSPACE\fP +.RS 4 +Display next k lines of text. Defaults to current screen size. +.RE +.sp +\fBz\fP +.RS 4 +Display next k lines of text. Defaults to current screen size. Argument becomes new default. +.RE +.sp +\fBRETURN\fP +.RS 4 +Display next k lines of text. Defaults to 1. Argument becomes new default. +.RE +.sp +\fBd\fP or \fB^D\fP +.RS 4 +Scroll k lines. Default is current scroll size, initially 11. Argument becomes new default. +.RE +.sp +\fBq\fP or \fBQ\fP or \fBINTERRUPT\fP +.RS 4 +Exit. +.RE +.sp +\fBs\fP +.RS 4 +Skip forward k lines of text. Defaults to 1. +.RE +.sp +\fBf\fP +.RS 4 +Skip forward k screenfuls of text. Defaults to 1. +.RE +.sp +\fBb\fP or \fB^B\fP +.RS 4 +Skip backwards k screenfuls of text. Defaults to 1. Only works with files, not pipes. +.RE +.sp +\fB\*(Aq\fP +.RS 4 +Go to the place where the last search started. +.RE +.sp +\fB=\fP +.RS 4 +Display current line number. +.RE +.sp +\fB/pattern\fP +.RS 4 +Search for kth occurrence of regular expression. Defaults to 1. +.RE +.sp +\fBn\fP +.RS 4 +Search for kth occurrence of last regular expression. Defaults to 1. +.RE +.sp +\fB!command\fP or \fB:!command\fP +.RS 4 +Execute \fIcommand\fP in a subshell. +.RE +.sp +\fBv\fP +.RS 4 +Start up an editor at current line. The editor is taken from the environment variable \fBVISUAL\fP if defined, or \fBEDITOR\fP if \fBVISUAL\fP is not defined, or defaults to \fBvi\fP(1) if neither \fBVISUAL\fP nor \fBEDITOR\fP is defined. +.RE +.sp +\fB^L\fP +.RS 4 +Redraw screen. +.RE +.sp +\fB:n\fP +.RS 4 +Go to kth next file. Defaults to 1. +.RE +.sp +\fB:p\fP +.RS 4 +Go to kth previous file. Defaults to 1. +.RE +.sp +\fB:f\fP +.RS 4 +Display current file name and line number. +.RE +.sp +\fB.\fP +.RS 4 +Repeat previous command. +.RE +.SH "ENVIRONMENT" +.sp +The \fBmore\fP command respects the following environment variables, if they exist: +.sp +\fBMORE\fP +.RS 4 +This variable may be set with favored options to \fBmore\fP. +.RE +.sp +\fBSHELL\fP +.RS 4 +Current shell in use (normally set by the shell at login time). +.RE +.sp +\fBTERM\fP +.RS 4 +The terminal type used by \fBmore\fP to get the terminal characteristics necessary to manipulate the screen. +.RE +.sp +\fBVISUAL\fP +.RS 4 +The editor the user prefers. Invoked when command key \fIv\fP is pressed. +.RE +.sp +\fBEDITOR\fP +.RS 4 +The editor of choice when \fBVISUAL\fP is not specified. +.RE +.sp +\fBPOSIXLY_CORRECT\fP +.RS 4 +Disable exit\-on\-eof (see option \fB\-e\fP for more details). +.RE +.SH "HISTORY" +.sp +The \fBmore\fP command appeared in 3.0BSD. This man page documents \fBmore\fP 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" +.sp +Eric Shienbrood, UC Berkeley. +.sp +Modified by Geoff Peck, UCB to add underlining, single spacing. +.sp +Modified by John Foderaro, UCB to add \-c and MORE environment variable. +.SH "SEE ALSO" +.sp +\fBless\fP(1), +\fBvi\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBmore\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/more.1.adoc b/text-utils/more.1.adoc new file mode 100644 index 0000000..b2f6842 --- /dev/null +++ b/text-utils/more.1.adoc @@ -0,0 +1,209 @@ +//po4a: entry man manual +//// +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) +//// += more(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: more + +== NAME + +more - display the contents of a file in a terminal + +== SYNOPSIS + +*more* [options] _file_ ... + +== DESCRIPTION + +*more* is a filter for paging through text one screenful at a time. This version is especially primitive. Users should realize that *less*(1) provides *more*(1) emulation plus extensive enhancements. + +== OPTIONS + +Options are also taken from the environment variable *MORE* (make sure to precede them with a dash (*-*)) but command-line options will override those. + +*-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. + +*-l*, *--logical*:: +Do not pause after any line containing a *^L* (form feed). + +*-e*, *--exit-on-eof*:: +Exit on End-Of-File, enabled by default if POSIXLY_CORRECT environment variable is not set or if not executed on terminal. + +*-f*, *--no-pause*:: +Count logical lines, rather than screen lines (i.e., long lines are not folded). + +*-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 *page*. + +*-c*, *--clean-print*:: +Do not scroll. Instead, paint each screen from the top, clearing the remainder of each line as it is displayed. + +*-s*, *--squeeze*:: +Squeeze multiple blank lines into one. + +*-u*, *--plain*:: +Suppress underlining. This option is silently ignored as backwards compatibility. + +*-n*, *--lines* _number_:: +Specify the _number_ of lines per screenful. The _number_ argument is a positive decimal integer. The *--lines* option shall override any values obtained from any other source, such as number of lines reported by terminal. + +**-**__number__:: +A numeric option means the same as *--lines* option argument. + +**+**__number__:: +Start displaying each file at line _number_. + +**+**/__string__:: +The _string_ to be searched in each file before starting to display it. + +include::man-common/help-version.adoc[] + +== COMMANDS + +Interactive commands for *more* are based on *vi*(1). Some commands may be preceded by a decimal number, called k in the descriptions below. In the following descriptions, *^X* means *control-X*. + +*h* or *?*:: +Help; display a summary of these commands. If you forget all other commands, remember this one. + +*SPACE*:: +Display next k lines of text. Defaults to current screen size. + +*z*:: +Display next k lines of text. Defaults to current screen size. Argument becomes new default. + +*RETURN*:: +Display next k lines of text. Defaults to 1. Argument becomes new default. + +*d* or *^D*:: +Scroll k lines. Default is current scroll size, initially 11. Argument becomes new default. + +*q* or *Q* or *INTERRUPT*:: +Exit. + +*s*:: +Skip forward k lines of text. Defaults to 1. + +*f*:: +Skip forward k screenfuls of text. Defaults to 1. + +*b* or *^B*:: +Skip backwards k screenfuls of text. Defaults to 1. Only works with files, not pipes. + +*'*:: +Go to the place where the last search started. + +*=*:: +Display current line number. + +*/pattern*:: +Search for kth occurrence of regular expression. Defaults to 1. + +*n*:: +Search for kth occurrence of last regular expression. Defaults to 1. + +*!command* or *:!command*:: +Execute _command_ in a subshell. + +*v*:: +Start up an editor at current line. The editor is taken from the environment variable *VISUAL* if defined, or *EDITOR* if *VISUAL* is not defined, or defaults to *vi*(1) if neither *VISUAL* nor *EDITOR* is defined. + +*^L*:: +Redraw screen. + +*:n*:: +Go to kth next file. Defaults to 1. + +*:p*:: +Go to kth previous file. Defaults to 1. + +*:f*:: +Display current file name and line number. + +*.*:: +Repeat previous command. + +== ENVIRONMENT + +The *more* command respects the following environment variables, if they exist: + +*MORE*:: +This variable may be set with favored options to *more*. + +*SHELL*:: +Current shell in use (normally set by the shell at login time). + +*TERM*:: +The terminal type used by *more* to get the terminal characteristics necessary to manipulate the screen. + +*VISUAL*:: +The editor the user prefers. Invoked when command key _v_ is pressed. + +*EDITOR*:: +The editor of choice when *VISUAL* is not specified. + +*POSIXLY_CORRECT*:: +Disable exit-on-eof (see option *-e* for more details). + +== HISTORY + +The *more* command appeared in 3.0BSD. This man page documents *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. + +== AUTHORS + +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. + +== SEE ALSO + +*less*(1), +*vi*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/more.c b/text-utils/more.c new file mode 100644 index 0000000..22adaab --- /dev/null +++ b/text-utils/more.c @@ -0,0 +1,2135 @@ +/* + * 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 "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_screen, + more_kc_skip_forward_line, + 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 */ + exit_on_eof:1, /* exit on EOF */ + 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_eof:1, /* EOF detected */ + 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 */ + no_tty_err:1, /* is stderr terminal */ + 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", _("Display the contents of a file in a terminal.")); + + 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", _(" -e, --exit-on-eof exit on end-of-file")); + 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' }, + { "exit-on-eof", no_argument, NULL, 'e' }, + { "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': + ctl->exit_on_eof = 1; + 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)) { + if (size == env_argc) { + size *= 2; + env_argv = xrealloc(env_argv, sizeof(char *) * size); + } + env_argv[env_argc++] = tok; + } + + 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); + + if (magic_error_msg) { + printf("%s: %s: %s\n", program_invocation_short_name, + _("magic failed"), magic_error_msg); + 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; + ctl->file_size = 0; + fflush(NULL); + + ctl->current_file = fopen(fs, "r"); + if (ctl->current_file == NULL) { + if (ctl->clear_line_ends) + putp(ctl->erase_line); + warn(_("cannot open %s"), fs); + return; + } + if (fstat(fileno(ctl->current_file), &st) != 0) { + warn(_("stat of %s failed"), fs); + return; + } + if ((st.st_mode & S_IFMT) == S_IFDIR) { + printf(_("\n*** %s: directory ***\n\n"), fs); + ctl->current_file = NULL; + return; + } + ctl->file_size = st.st_size; + if (0 < ctl->file_size && 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); +} + +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 && 0 < ctl->file_size) { + int position = ((ctl->file_position * 100) / ctl->file_size); + if (position == 100) { + erase_to_col(ctl, 0); + ctl->prompt_len += printf(_("(END)")); + } else { + ctl->prompt_len += printf("(%d%%)", position); + } + } else if (ctl->is_eof) { + erase_to_col(ctl, 0); + ctl->prompt_len += printf(_("(END)")); + } + + 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_jump_lines_per_screen; + 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_jump_lines_per_screen; + 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; + return cmd; + break; + case 'f': + case CTRL('F'): + cmd.key = more_kc_skip_forward_screen; + break; + case 's': + cmd.key = more_kc_skip_forward_line; + 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 = 0, offset; + + if (!ctl->no_tty_in) + xtra += strlen(ctl->file_names[ctl->argv_position]) + 1; + if (ctl->shell_line) + xtra += 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 __attribute__((__format__ (__printf__, 3, 4))) + execute(struct more_control *ctl, char *filename, const 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); + ignore_result( 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()) + && drop_permissions() != 0) + err(EXIT_FAILURE, _("drop permissions 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, stderr); + 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, size_t buflen, 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")) { + snprintf(cmdbuf, buflen, "-c %d", n); + split = 1; + } else + snprintf(cmdbuf, buflen, "+%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_screen: + if (skip_forwards(ctl, cmd.number, 'f')) + retval = ctl->lines_per_screen; + done = 1; + break; + case more_kc_skip_forward_line: + if (skip_forwards(ctl, cmd.number, 's')) + 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, sizeof(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) { + nchars = get_line(ctl, &length); + ctl->is_eof = nchars == EOF; + if (ctl->is_eof && ctl->exit_on_eof) { + if (ctl->clear_line_ends) + putp(ctl->clear_rest); + return; + } + if (ctl->squeeze_spaces && length == 0 && prev_len == 0 && !ctl->is_eof) + 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); + + c = more_getc(ctl); + ctl->is_eof = c == EOF; + + if (ctl->is_eof && ctl->exit_on_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) + && 0 < ctl->file_size) { + 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); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + puts(ctl->file_names[ctl->argv_position]); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + 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); + ctl->no_tty_err = 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++; + + ctl.exit_on_eof = getenv("POSIXLY_CORRECT") ? 0 : 1; + + if ((s = getenv("MORE")) != NULL) + env_argscan(&ctl, s); + + argscan(&ctl, argc, argv); + + /* clear any inherited settings */ + signal(SIGCHLD, SIG_DFL); + + initterm(&ctl); + + if (ctl.no_tty_err) + /* exit when we cannot read user's input */ + ctl.exit_on_eof = 1; + +#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..84817b9 --- /dev/null +++ b/text-utils/pg.1 @@ -0,0 +1,238 @@ +'\" t +.\" Title: pg +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "PG" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +pg \- browse pagewise through text files +.SH "SYNOPSIS" +.sp +\fBpg\fP \fB\-*\fIamount\fP *\-p\fP \fIprompt\fP \fB\-cefnrs\fP +line +/pattern/ file_ ... +.SH "DESCRIPTION" +.sp +\fBpg\fP displays a text file on a 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. +.sp +If no filename is given on the command line, \fBpg\fP reads from standard input. If standard output is not a terminal, \fBpg\fP acts like \fBcat\fP(1) but precedes each file with its name if there is more than one. +.sp +If input comes from a pipe, \fBpg\fP stores the data in a buffer file while reading, to make navigation possible. +.SH "OPTIONS" +.sp +\fBpg\fP accepts the following options: +.sp +\fB+\fP\fInumber\fP +.RS 4 +Start at the given line number. +.RE +.sp +\fB+/\fP\fIpattern\fP*/* +.RS 4 +Start at the line containing the Basic Regular Expression \fIpattern\fP given. +.RE +.sp +\fB\-\fP\fInumber\fP +.RS 4 +The number of lines per page. By default, this is the number of CRT lines minus one. +.RE +.sp +\fB\-c\fP +.RS 4 +Clear the screen before a page is displayed, if the terminfo entry for the terminal provides this capability. +.RE +.sp +\fB\-e\fP +.RS 4 +Do not pause and display \fI(EOF)\fP at the end of a file. +.RE +.sp +\fB\-f\fP +.RS 4 +Do not split long lines. +.RE +.sp +\fB\-n\fP +.RS 4 +Without this option, commands must be terminated by a newline character. +.sp +With this option, \fBpg\fP advances once a command letter is entered. +.RE +.sp +\fB\-p\fP \fIstring\fP +.RS 4 +Instead of the normal prompt \fI:\fP, \fIstring\fP is displayed. If \fIstring\fP contains \fB%d\fP, its first occurrence is replaced by the number of the current page. +.RE +.sp +\fB\-r\fP +.RS 4 +Disallow the shell escape. +.RE +.sp +\fB\-s\fP +.RS 4 +Print messages in \fIstandout\fP mode, if the terminfo entry for the terminal provides this capability. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "COMMANDS" +.sp +The following commands may be entered at the prompt. Commands preceded by \fIi\fP in this document accept a number as argument, positive or negative. If this argument starts with \fB+\fP or \fB\-\fP, it is interpreted relative to the current position in the input file, otherwise relative to the beginning. +.sp +\fIi\fP\fB<Enter>\fP +.RS 4 +Display the next or the indicated page. +.RE +.sp +\fIi\fP\fBd\fP or \fB^D\fP +.RS 4 +Display the next halfpage. If \fIi\fP is given, it is always interpreted relative to the current position. +.RE +.sp +\fIi\fP\fBl\fP +.RS 4 +Display the next or the indicated line. +.RE +.sp +\fIi\fP\fBf\fP +.RS 4 +Skip a page forward. \fIi\fP must be a positive number and is always interpreted relative to the current position. +.RE +.sp +\fIi\fP\fBw\fP or \fIi\fP\fBz\fP +.RS 4 +As \fB<Enter>\fP except that \fIi\fP becomes the new page size. +.RE +.sp +\fB.\fP or \fB^L\fP +.RS 4 +Redraw the screen. +.RE +.sp +\fB$\fP +.RS 4 +Advance to the last line of the input file. +.RE +.sp +\fIi\fP\fB/\fP\fIpattern\fP\fB/\fP +.RS 4 +Search forward until the first or the \fIi\fP\-th occurrence of the Basic Regular Expression \fIpattern\fP is found. The search starts after the current page and stops at the end of the file. No wrap\-around is performed. \fIi\fP must be a positive number. +.RE +.sp +\fIi\fP\fB?\fP\fIpattern\fP\fB?\fP or \fIi\fP\fB\fP\fIpattern\fP\fB\fP +.RS 4 +Search backward until the first or the \fIi\fP\-th occurrence of the Basic Regular Expression \fIpattern\fP is found. The search starts before the current page and stops at the beginning of the file. No wrap\-around is performed. \fIi\fP must be a positive number. +.RE +.sp +The search commands accept an added letter. If \fBt\fP is given, the line containing the pattern is displayed at the top of the screen, which is the default. \fBm\fP selects the middle and \fBb\fP the bottom of the screen. The selected position is used in following searches, too. +.sp +\fIi\fP\fBn\fP +.RS 4 +Advance to the next file or \fIi\fP files forward. +.RE +.sp +\fIi\fP\fBp\fP +.RS 4 +Reread the previous file or \fIi\fP files backward. +.RE +.sp +\fBs\fP \fIfilename\fP +.RS 4 +Save the current file to the given \fIfilename\fP. +.RE +.sp +\fBh\fP +.RS 4 +Display a command summary. +.RE +.sp +\fB!\fP\fIcommand\fP +.RS 4 +Execute \fIcommand\fP using the shell. +.RE +.sp +\fBq\fP or \fBQ\fP +.RS 4 +Quit. +.RE +.sp +If the user presses the interrupt or quit key while \fBpg\fP reads from the input file or writes on the terminal, \fBpg\fP will immediately display the prompt. In all other situations these keys will terminate \fBpg\fP. +.SH "ENVIRONMENT" +.sp +The following environment variables affect the behavior of \fBpg\fP: +.sp +\fBCOLUMNS\fP +.RS 4 +Overrides the system\-supplied number of columns if set. +.RE +.sp +\fBLANG\fP, \fBLC_ALL\fP, \fBLC_COLLATE\fP, \fBLC_CTYPE\fP, \fBLC_MESSAGES\fP +.RS 4 +See \fBlocale\fP(7). +.RE +.sp +\fBLINES\fP +.RS 4 +Overrides the system\-supplied number of lines if set. +.RE +.sp +\fBSHELL\fP +.RS 4 +Used by the \fB!\fP command. +.RE +.sp +\fBTERM\fP +.RS 4 +Determines the terminal type. +.RE +.SH "NOTES" +.sp +\fBpg\fP expects the terminal tabulators to be set every eight positions. +.sp +Files that include NUL characters cannot be displayed by \fBpg\fP. +.SH "SEE ALSO" +.sp +\fBcat\fP(1), +\fBmore\fP(1), +\fBsh\fP(1p), +\fBterminfo\fP(5), +\fBlocale\fP(7), +\fBregex\fP(7), +\fBterm\fP(7) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBpg\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/pg.1.adoc b/text-utils/pg.1.adoc new file mode 100644 index 0000000..62b456e --- /dev/null +++ b/text-utils/pg.1.adoc @@ -0,0 +1,158 @@ +//po4a: entry man manual +// Copyright 2001 Gunnar Ritter += pg(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: pg + +== NAME + +pg - browse pagewise through text files + +== SYNOPSIS + +*pg* *-*_amount_ *-p* _prompt_ *-cefnrs* +line +/pattern/ file_ ... + +== DESCRIPTION + +*pg* displays a text file on a 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. + +If no filename is given on the command line, *pg* reads from standard input. If standard output is not a terminal, *pg* acts like *cat*(1) but precedes each file with its name if there is more than one. + +If input comes from a pipe, *pg* stores the data in a buffer file while reading, to make navigation possible. + +== OPTIONS + +*pg* accepts the following options: + +**+**__number__:: +Start at the given line number. + +**+/**__pattern__*/*:: +Start at the line containing the Basic Regular Expression _pattern_ given. + +**-**__number__:: +The number of lines per page. By default, this is the number of CRT lines minus one. + +*-c*:: +Clear the screen before a page is displayed, if the terminfo entry for the terminal provides this capability. + +*-e*:: +Do not pause and display _(EOF)_ at the end of a file. + +*-f*:: +Do not split long lines. + +*-n*:: +Without this option, commands must be terminated by a newline character. ++ +With this option, *pg* advances once a command letter is entered. + +*-p* _string_:: +Instead of the normal prompt _:_, _string_ is displayed. If _string_ contains *%d*, its first occurrence is replaced by the number of the current page. + +*-r*:: +Disallow the shell escape. + +*-s*:: +Print messages in _standout_ mode, if the terminfo entry for the terminal provides this capability. + +include::man-common/help-version.adoc[] + +== COMMANDS + +The following commands may be entered at the prompt. Commands preceded by _i_ in this document accept a number as argument, positive or negative. If this argument starts with *+* or *-*, it is interpreted relative to the current position in the input file, otherwise relative to the beginning. + +__i__**<Enter>**:: +Display the next or the indicated page. + +__i__**d** or *^D*:: +Display the next halfpage. If _i_ is given, it is always interpreted relative to the current position. + +__i__**l**:: +Display the next or the indicated line. + +__i__**f**:: +Skip a page forward. _i_ must be a positive number and is always interpreted relative to the current position. + +__i__**w** or __i__**z**:: +As *<Enter>* except that _i_ becomes the new page size. + +*.* or *^L*:: +Redraw the screen. + +*$*:: +Advance to the last line of the input file. + +__i__**/**__pattern__**/**:: +Search forward until the first or the _i_-th occurrence of the Basic Regular Expression _pattern_ is found. The search starts after the current page and stops at the end of the file. No wrap-around is performed. _i_ must be a positive number. + +__i__**?**__pattern__**?** or __i__**^**__pattern__**^**:: +Search backward until the first or the _i_-th occurrence of the Basic Regular Expression _pattern_ is found. The search starts before the current page and stops at the beginning of the file. No wrap-around is performed. _i_ must be a positive number. + +The search commands accept an added letter. If *t* is given, the line containing the pattern is displayed at the top of the screen, which is the default. *m* selects the middle and *b* the bottom of the screen. The selected position is used in following searches, too. + +__i__**n**:: +Advance to the next file or _i_ files forward. + +__i__**p**:: +Reread the previous file or _i_ files backward. + +*s* _filename_:: +Save the current file to the given _filename_. + +*h*:: +Display a command summary. + +**!**__command__:: +Execute _command_ using the shell. + +*q* or *Q*:: +Quit. + +If the user presses the interrupt or quit key while *pg* reads from the input file or writes on the terminal, *pg* will immediately display the prompt. In all other situations these keys will terminate *pg*. + +== ENVIRONMENT + +The following environment variables affect the behavior of *pg*: + +*COLUMNS*:: +Overrides the system-supplied number of columns if set. + +*LANG*, *LC_ALL*, *LC_COLLATE*, *LC_CTYPE*, *LC_MESSAGES*:: +See *locale*(7). + +*LINES*:: +Overrides the system-supplied number of lines if set. + +*SHELL*:: +Used by the *!* command. + +*TERM*:: +Determines the terminal type. + +== NOTES + +*pg* expects the terminal tabulators to be set every eight positions. + +Files that include NUL characters cannot be displayed by *pg*. + +== SEE ALSO + +*cat*(1), +*more*(1), +*sh*(1p), +*terminfo*(5), +*locale*(7), +*regex*(7), +*term*(7) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/pg.c b/text-utils/pg.c new file mode 100644 index 0000000..adb3840 --- /dev/null +++ b/text-utils/pg.c @@ -0,0 +1,1690 @@ +/* + * 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 + if (initialized == 0) { + uint32_t tmp = 0; + + if (ul_strtou32(getenv("LINES"), &tmp, 10) == 0) + envlines = tmp; + if (ul_strtou32(getenv("COLUMNS"), &tmp, 10) == 0) + envcols = tmp; + + /* 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 (ul_strtos32(*buf == '+' ? buf + 1 : buf, &i, 10) != 0) { + i = -1; + } + 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..3351717 --- /dev/null +++ b/text-utils/rev.1 @@ -0,0 +1,66 @@ +'\" t +.\" Title: rev +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "REV" "1" "2023-11-21" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +rev \- reverse lines characterwise +.SH "SYNOPSIS" +.sp +\fBrev\fP [option] [\fIfile\fP...] +.SH "DESCRIPTION" +.sp +The \fBrev\fP 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. +.sp +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 then allocating the memory for the file may be unsuccessful. +.SH "OPTIONS" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.sp +\fB\-0\fP, \fB\-\-zero\fP +.RS 4 +\fIZero termination\fP. Use the byte \*(Aq\(rs0\*(Aq as line separator. +.RE +.SH "SEE ALSO" +.sp +\fBtac\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBrev\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/rev.1.adoc b/text-utils/rev.1.adoc new file mode 100644 index 0000000..3d40273 --- /dev/null +++ b/text-utils/rev.1.adoc @@ -0,0 +1,74 @@ +//po4a: entry man manual +//// +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 +//// += rev(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: rev + +== NAME + +rev - reverse lines characterwise + +== SYNOPSIS + +*rev* [option] [_file_...] + +== DESCRIPTION + +The *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. + +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 then allocating the memory for the file may be unsuccessful. + +== OPTIONS + +include::man-common/help-version.adoc[] + +*-0*, *--zero*:: +_Zero termination_. Use the byte '\0' as line separator. + +== SEE ALSO + +*tac*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/rev.c b/text-utils/rev.c new file mode 100644 index 0000000..fbf04d1 --- /dev/null +++ b/text-utils/rev.c @@ -0,0 +1,205 @@ +/*- + * 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; + } +} + +static size_t read_line(wchar_t sep, wchar_t *str, size_t n, FILE *stream) +{ + size_t r = 0; + while (r < n) { + wint_t c = fgetwc(stream); + if (c == WEOF) + break; + str[r++] = c; + if ((wchar_t) c == sep) + break; + } + return r; +} + +static void write_line(wchar_t *str, size_t n, FILE *stream) +{ + for (size_t i = 0; i < n; i++) + fputwc(str[i], stream); +} + +int main(int argc, char *argv[]) +{ + char const *filename = "stdin"; + wchar_t *buf; + wchar_t sep = L'\n'; + size_t len, bufsiz = BUFSIZ; + FILE *fp = stdin; + int ch, rval = EXIT_SUCCESS; + uintmax_t line; + + static const struct option longopts[] = { + { "zero", no_argument, NULL, '0' }, + { "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, "Vh0", longopts, NULL)) != -1) + switch(ch) { + case '0': + sep = L'\0'; + break; + 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 (!feof(fp)) { + len = read_line(sep, buf, bufsiz, fp); + if (len == 0) + continue; + + /* This is my hack from setpwnam.c -janl */ + while (len == bufsiz && !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 */ + len += read_line(sep, &buf[len], bufsiz/2, fp); + } + reverse_str(buf, buf[len - 1] == sep ? len - 1 : len); + write_line(buf, len, 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..2906ee3 --- /dev/null +++ b/text-utils/ul.1 @@ -0,0 +1,88 @@ +'\" t +.\" Title: ul +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: User Commands +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "UL" "1" "2023-10-23" "util\-linux 2.39.3" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +ul \- do underlining +.SH "SYNOPSIS" +.sp +\fBul\fP [options] [\fIfile\fP...] +.SH "DESCRIPTION" +.sp +\fBul\fP 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 \fBTERM\fP. The \fIterminfo\fP 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, \fBul\fP degenerates to \fBcat\fP(1). If the terminal cannot underline, underlining is ignored. +.SH "OPTIONS" +.sp +\fB\-i\fP, \fB\-\-indicated\fP +.RS 4 +Underlining is indicated by a separate line containing appropriate dashes `\-\*(Aq; this is useful when you want to look at the underlining which is present in an \fBnroff\fP output stream on a crt\-terminal. +.RE +.sp +\fB\-t\fP, \fB\-T\fP, \fB\-\-terminal\fP \fIterminal\fP +.RS 4 +Override the environment variable \fBTERM\fP with the specified \fIterminal\fP type. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "ENVIRONMENT" +.sp +The following environment variable is used: +.sp +\fBTERM\fP +.RS 4 +The \fBTERM\fP variable is used to relate a tty device with its device capability description (see \fBterminfo\fP(5)). \fBTERM\fP is set at login time, either by the default terminal type specified in \fI/etc/ttys\fP or as set during the login process by the user in their \fIlogin\fP file (see \fBsetenv\fP(3)). +.RE +.SH "HISTORY" +.sp +The \fBul\fP command appeared in 3.0BSD. +.SH "BUGS" +.sp +\fBnroff\fP 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" +.sp +\fBcolcrt\fP(1), +\fBlogin\fP(1), +\fBman\fP(1), +\fBnroff\fP(1), +\fBsetenv\fP(3), +\fBterminfo\fP(5) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBul\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/text-utils/ul.1.adoc b/text-utils/ul.1.adoc new file mode 100644 index 0000000..bc19e61 --- /dev/null +++ b/text-utils/ul.1.adoc @@ -0,0 +1,95 @@ +//po4a: entry man manual +//// +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 +//// += ul(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: ul + +== NAME + +ul - do underlining + +== SYNOPSIS + +*ul* [options] [_file_...] + +== DESCRIPTION + +*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 *TERM*. The _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, *ul* degenerates to *cat*(1). If the terminal cannot underline, underlining is ignored. + +== OPTIONS + +*-i*, *--indicated*:: +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 *nroff* output stream on a crt-terminal. + +*-t*, *-T*, *--terminal* _terminal_:: +Override the environment variable *TERM* with the specified _terminal_ type. + +include::man-common/help-version.adoc[] + +== ENVIRONMENT + +The following environment variable is used: + +*TERM*:: +The *TERM* variable is used to relate a tty device with its device capability description (see *terminfo*(5)). *TERM* is set at login time, either by the default terminal type specified in _/etc/ttys_ or as set during the login process by the user in their _login_ file (see *setenv*(3)). + +== HISTORY + +The *ul* command appeared in 3.0BSD. + +== BUGS + +*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. + +== SEE ALSO + +*colcrt*(1), +*login*(1), +*man*(1), +*nroff*(1), +*setenv*(3), +*terminfo*(5) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/text-utils/ul.c b/text-utils/ul.c new file mode 100644 index 0000000..d5bca45 --- /dev/null +++ b/text-utils/ul.c @@ -0,0 +1,646 @@ +/* + * 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" + +#define ESC '\033' +#define SO '\016' +#define SI '\017' +#define HFWD '9' +#define HREV '8' +#define FREV '7' + +enum { + NORMAL_CHARSET = 0, /* Must be zero, see initbuf() */ + ALTERNATIVE_CHARSET = 1 << 0, /* Reverse */ + SUPERSCRIPT = 1 << 1, /* Dim */ + SUBSCRIPT = 1 << 2, /* Dim | Ul */ + UNDERLINE = 1 << 3, /* Ul */ + BOLD = 1 << 4, /* Bold */ +}; + +struct term_caps { + char *curs_up; + char *curs_right; + char *curs_left; + char *enter_standout; + char *exit_standout; + char *enter_underline; + char *exit_underline; + char *enter_dim; + char *enter_bold; + char *enter_reverse; + char *under_char; + char *exit_attributes; +}; + +struct ul_char { + wchar_t c_char; + int c_width; + char c_mode; +}; + +struct ul_ctl { + size_t column; + size_t max_column; + int half_position; + int up_line; + int mode; + int current_mode; + size_t buflen; + struct ul_char *buf; + unsigned int + indicated_opt:1, + must_use_uc:1, + must_overstrike: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(_("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); + printf(USAGE_HELP_OPTIONS(30)); + + printf(USAGE_MAN_TAIL("ul(1)")); + + exit(EXIT_SUCCESS); +} + +static void need_column(struct ul_ctl *ctl, size_t new_max) +{ + ctl->max_column = new_max; + + while (new_max >= ctl->buflen) { + ctl->buflen *= 2; + ctl->buf = xrealloc(ctl->buf, sizeof(struct ul_char) * ctl->buflen); + } +} + +static void set_column(struct ul_ctl *ctl, size_t column) +{ + ctl->column = column; + + if (ctl->max_column < ctl->column) + need_column(ctl, ctl->column); +} + +static void init_buffer(struct ul_ctl *ctl) +{ + if (ctl->buf == NULL) { + /* First time. */ + ctl->buflen = BUFSIZ; + ctl->buf = xcalloc(ctl->buflen, sizeof(struct ul_char)); + } else + /* assumes NORMAL_CHARSET == 0 */ + memset(ctl->buf, 0, sizeof(struct ul_char) * ctl->max_column); + + set_column(ctl, 0); + ctl->max_column = 0; + ctl->mode &= ALTERNATIVE_CHARSET; +} + +static void init_term_caps(struct ul_ctl *ctl, struct term_caps *const tcs) +{ + tcs->curs_up = tigetstr("cuu1"); + tcs->curs_right = tigetstr("cuf1"); + tcs->curs_left = tigetstr("cub1"); + if (tcs->curs_left == NULL) + tcs->curs_left = "\b"; + + tcs->enter_standout = tigetstr("smso"); + tcs->exit_standout = tigetstr("rmso"); + tcs->enter_underline = tigetstr("smul"); + tcs->exit_underline = tigetstr("rmul"); + tcs->enter_dim = tigetstr("dim"); + tcs->enter_bold = tigetstr("bold"); + tcs->enter_reverse = tigetstr("rev"); + tcs->exit_attributes = tigetstr("sgr0"); + + if (!tcs->enter_bold && tcs->enter_reverse) + tcs->enter_bold = tcs->enter_reverse; + + if (!tcs->enter_bold && tcs->enter_standout) + tcs->enter_bold = tcs->enter_standout; + + if (!tcs->enter_underline && tcs->enter_standout) { + tcs->enter_underline = tcs->enter_standout; + tcs->exit_underline = tcs->exit_standout; + } + + if (!tcs->enter_dim && tcs->enter_standout) + tcs->enter_dim = tcs->enter_standout; + + if (!tcs->enter_reverse && tcs->enter_standout) + tcs->enter_reverse = tcs->enter_standout; + + if (!tcs->exit_attributes && tcs->exit_standout) + tcs->exit_attributes = tcs->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. + */ + tcs->under_char = tigetstr("uc"); + ctl->must_use_uc = (tcs->under_char && !tcs->enter_underline); + + if ((tigetflag("os") && tcs->enter_bold == NULL) || + (tigetflag("ul") && tcs->enter_underline == NULL + && tcs->under_char == NULL)) + ctl->must_overstrike = 1; +} + +static void sig_handler(int signo __attribute__((__unused__))) +{ + _exit(EXIT_SUCCESS); +} + +static int ul_putwchar(int c) +{ + if (putwchar(c) == WEOF) + return EOF; + return c; +} + +static void print_line(char *line) +{ + if (line == NULL) + return; + tputs(line, STDOUT_FILENO, ul_putwchar); +} + +static void ul_setmode(struct ul_ctl *ctl, struct term_caps const *const tcs, + int new_mode) +{ + if (!ctl->indicated_opt) { + if (ctl->current_mode != NORMAL_CHARSET && new_mode != NORMAL_CHARSET) + ul_setmode(ctl, tcs, NORMAL_CHARSET); + + switch (new_mode) { + case NORMAL_CHARSET: + switch (ctl->current_mode) { + case NORMAL_CHARSET: + break; + case UNDERLINE: + print_line(tcs->exit_underline); + break; + default: + /* This includes standout */ + print_line(tcs->exit_attributes); + break; + } + break; + case ALTERNATIVE_CHARSET: + print_line(tcs->enter_reverse); + break; + case SUPERSCRIPT: + /* + * This only works on a few terminals. + * It should be fixed. + */ + print_line(tcs->enter_underline); + print_line(tcs->enter_dim); + break; + case SUBSCRIPT: + print_line(tcs->enter_dim); + break; + case UNDERLINE: + print_line(tcs->enter_underline); + break; + case BOLD: + print_line(tcs->enter_bold); + break; + default: + /* + * We should have some provision here for multiple modes + * on at once. This will have to come later. + */ + print_line(tcs->enter_standout); + break; + } + } + ctl->current_mode = new_mode; +} + +static void indicate_attribute(struct ul_ctl *ctl) +{ + size_t i; + wchar_t *buf = xcalloc(ctl->max_column + 1, sizeof(wchar_t)); + wchar_t *p = buf; + + for (i = 0; i < ctl->max_column; i++) { + switch (ctl->buf[i].c_mode) { + case NORMAL_CHARSET: *p++ = ' '; break; + case ALTERNATIVE_CHARSET: *p++ = 'g'; break; + case SUPERSCRIPT: *p++ = '^'; break; + case SUBSCRIPT: *p++ = 'v'; break; + case UNDERLINE: *p++ = '_'; break; + case BOLD: *p++ = '!'; break; + default: *p++ = 'X'; break; + } + } + + for (*p = ' '; *p == ' '; p--) + *p = 0; + + fputws(buf, stdout); + putwchar('\n'); + free(buf); +} + +static void output_char(struct ul_ctl *ctl, struct term_caps const *const tcs, + wint_t c, int width) +{ + int i; + + putwchar(c); + if (ctl->must_use_uc && (ctl->current_mode & UNDERLINE)) { + for (i = 0; i < width; i++) + print_line(tcs->curs_left); + for (i = 0; i < width; i++) + print_line(tcs->under_char); + } +} + +/* + * For terminals that can overstrike, overstrike underlines and bolds. + * We don't do anything with halfline ups and downs, or Greek. + */ +static void overstrike(struct ul_ctl *ctl) +{ + size_t i; + wchar_t *buf = xcalloc(ctl->max_column + 1, sizeof(wchar_t)); + wchar_t *p = buf; + int had_bold = 0; + + /* Set up overstrike buffer */ + for (i = 0; i < ctl->max_column; i++) { + switch (ctl->buf[i].c_mode) { + case NORMAL_CHARSET: + default: + *p++ = ' '; + break; + case UNDERLINE: + *p++ = '_'; + break; + case BOLD: + *p++ = ctl->buf[i].c_char; + if (1 < ctl->buf[i].c_width) + i += ctl->buf[i].c_width - 1; + had_bold = 1; + break; + } + } + + putwchar('\r'); + for (*p = ' '; *p == ' '; p--) + *p = 0; + fputws(buf, stdout); + + if (had_bold) { + putwchar('\r'); + for (p = buf; *p; p++) + putwchar(*p == '_' ? ' ' : *p); + putwchar('\r'); + for (p = buf; *p; p++) + putwchar(*p == '_' ? ' ' : *p); + } + free(buf); +} + +static void flush_line(struct ul_ctl *ctl, struct term_caps const *const tcs) +{ + int last_mode; + size_t i; + int had_mode = 0; + + last_mode = NORMAL_CHARSET; + for (i = 0; i < ctl->max_column; i++) { + if (ctl->buf[i].c_mode != last_mode) { + had_mode = 1; + ul_setmode(ctl, tcs, ctl->buf[i].c_mode); + last_mode = ctl->buf[i].c_mode; + } + if (ctl->buf[i].c_char == '\0') { + if (ctl->up_line) + print_line(tcs->curs_right); + else + output_char(ctl, tcs, ' ', 1); + } else + output_char(ctl, tcs, ctl->buf[i].c_char, ctl->buf[i].c_width); + if (1 < ctl->buf[i].c_width) + i += ctl->buf[i].c_width - 1; + } + if (last_mode != NORMAL_CHARSET) + ul_setmode(ctl, tcs, NORMAL_CHARSET); + if (ctl->must_overstrike && had_mode) + overstrike(ctl); + putwchar('\n'); + if (ctl->indicated_opt && had_mode) + indicate_attribute(ctl); + fflush(stdout); + if (ctl->up_line) + ctl->up_line--; + init_buffer(ctl); +} + +static void forward(struct ul_ctl *ctl, struct term_caps const *const tcs) +{ + int old_column, old_maximum; + + old_column = ctl->column; + old_maximum = ctl->max_column; + flush_line(ctl, tcs); + set_column(ctl, old_column); + ctl->max_column = old_maximum; +} + +static void reverse(struct ul_ctl *ctl, struct term_caps const *const tcs) +{ + ctl->up_line++; + forward(ctl, tcs); + print_line(tcs->curs_up); + print_line(tcs->curs_up); + ctl->up_line++; +} + +static int handle_escape(struct ul_ctl *ctl, struct term_caps const *const tcs, FILE *f) +{ + wint_t c; + + switch (c = getwc(f)) { + case HREV: + if (0 < ctl->half_position) { + ctl->mode &= ~SUBSCRIPT; + ctl->half_position--; + } else if (ctl->half_position == 0) { + ctl->mode |= SUPERSCRIPT; + ctl->half_position--; + } else { + ctl->half_position = 0; + reverse(ctl, tcs); + } + return 0; + case HFWD: + if (ctl->half_position < 0) { + ctl->mode &= ~SUPERSCRIPT; + ctl->half_position++; + } else if (ctl->half_position == 0) { + ctl->mode |= SUBSCRIPT; + ctl->half_position++; + } else { + ctl->half_position = 0; + forward(ctl, tcs); + } + return 0; + case FREV: + reverse(ctl, tcs); + return 0; + default: + /* unknown escape */ + ungetwc(c, f); + return 1; + } +} + +static void filter(struct ul_ctl *ctl, struct term_caps const *const tcs, FILE *f) +{ + wint_t c; + int i, width; + + while ((c = getwc(f)) != WEOF) { + switch (c) { + case '\b': + set_column(ctl, ctl->column && 0 < ctl->column ? ctl->column - 1 : 0); + continue; + case '\t': + set_column(ctl, (ctl->column + 8) & ~07); + continue; + case '\r': + set_column(ctl, 0); + continue; + case SO: + ctl->mode |= ALTERNATIVE_CHARSET; + continue; + case SI: + ctl->mode &= ~ALTERNATIVE_CHARSET; + continue; + case ESC: + if (handle_escape(ctl, tcs, f)) { + c = getwc(f); + errx(EXIT_FAILURE, + _("unknown escape sequence in input: %o, %o"), ESC, c); + } + continue; + case '_': + if (ctl->buf[ctl->column].c_char || ctl->buf[ctl->column].c_width < 0) { + while (ctl->buf[ctl->column].c_width < 0 && 0 < ctl->column) + ctl->column--; + width = ctl->buf[ctl->column].c_width; + for (i = 0; i < width; i++) + ctl->buf[ctl->column++].c_mode |= UNDERLINE | ctl->mode; + set_column(ctl, 0 < ctl->column ? ctl->column : 0); + continue; + } + ctl->buf[ctl->column].c_char = '_'; + ctl->buf[ctl->column].c_width = 1; + /* fallthrough */ + case ' ': + set_column(ctl, ctl->column + 1); + continue; + case '\n': + flush_line(ctl, tcs); + continue; + case '\f': + flush_line(ctl, tcs); + putwchar('\f'); + continue; + default: + if (!iswprint(c)) + /* non printable */ + continue; + width = wcwidth(c); + need_column(ctl, ctl->column + width); + if (ctl->buf[ctl->column].c_char == '\0') { + ctl->buf[ctl->column].c_char = c; + for (i = 0; i < width; i++) + ctl->buf[ctl->column + i].c_mode = ctl->mode; + ctl->buf[ctl->column].c_width = width; + for (i = 1; i < width; i++) + ctl->buf[ctl->column + i].c_width = -1; + } else if (ctl->buf[ctl->column].c_char == '_') { + ctl->buf[ctl->column].c_char = c; + for (i = 0; i < width; i++) + ctl->buf[ctl->column + i].c_mode |= UNDERLINE | ctl->mode; + ctl->buf[ctl->column].c_width = width; + for (i = 1; i < width; i++) + ctl->buf[ctl->column + i].c_width = -1; + } else if ((wint_t) ctl->buf[ctl->column].c_char == c) { + for (i = 0; i < width; i++) + ctl->buf[ctl->column + i].c_mode |= BOLD | ctl->mode; + } else { + width = ctl->buf[ctl->column].c_width; + for (i = 0; i < width; i++) + ctl->buf[ctl->column + i].c_mode = ctl->mode; + } + set_column(ctl, ctl->column + width); + continue; + } + } + if (ctl->max_column) + flush_line(ctl, tcs); +} + +int main(int argc, char **argv) +{ + int c, ret, opt_terminal = 0; + char *termtype; + struct term_caps tcs = { 0 }; + struct ul_ctl ctl = { .current_mode = NORMAL_CHARSET }; + 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; + opt_terminal = 1; + break; + case 'i': + ctl.indicated_opt = 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 (opt_terminal) + warnx(_("terminal `%s' is not known, defaulting to `dumb'"), + termtype); + setupterm("dumb", STDOUT_FILENO, (int *)0); + break; + } + + init_term_caps(&ctl, &tcs); + init_buffer(&ctl); + + if (optind == argc) + filter(&ctl, &tcs, stdin); + else { + for (; optind < argc; optind++) { + f = fopen(argv[optind], "r"); + if (!f) + err(EXIT_FAILURE, _("cannot open %s"), argv[optind]); + filter(&ctl, &tcs, f); + fclose(f); + } + } + + free(ctl.buf); + del_curterm(cur_term); + return EXIT_SUCCESS; +} |