diff options
Diffstat (limited to 'src/pr.c')
-rw-r--r-- | src/pr.c | 2856 |
1 files changed, 2856 insertions, 0 deletions
diff --git a/src/pr.c b/src/pr.c new file mode 100644 index 0000000..4c17c00 --- /dev/null +++ b/src/pr.c @@ -0,0 +1,2856 @@ +/* pr -- convert text files for printing. + Copyright (C) 1988-2022 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* By Pete TerMaat, with considerable refinement by Roland Huebner. */ + +/* Things to watch: Sys V screws up on ... + pr -n -3 -s: /usr/dict/words + pr -m -o10 -n /usr/dict/words{,,,} + pr -6 -a -n -o5 /usr/dict/words + + Ideas: + + Keep a things_to_do list of functions to call when we know we have + something to print. Cleaner than current series of checks. + + Improve the printing of control prefixes. + + Expand the file name in the centered header line to a full file name. + + + Concept: + + If the input_tab_char differs from the default value TAB + ('-e[CHAR[...]]' is used), any input text tab is expanded to the + default width of 8 spaces (compare char_to_clump). - Same as SunOS + does. + + The treatment of the number_separator (compare add_line_number): + The default value TAB of the number_separator ('-n[SEP[...]]') doesn't + be thought to be an input character. An optional '-e'-input has no + effect. + - With single column output + only one POSIX requirement has to be met: + The default n-separator should be a TAB. The consequence is a + different width between the number and the text if the output position + of the separator changes, i.e., it depends upon the left margin used. + That's not nice but easy-to-use together with the defaults of other + utilities, e.g. sort or cut. - Same as SunOS does. + - With multicolumn output + two conflicting POSIX requirements exist: + First "default n-separator is TAB", second "output text columns shall + be of equal width". Moreover POSIX specifies the number+separator a + part of the column, together with '-COLUMN' and '-a -COLUMN'. + (With -m output the number shall occupy each line only once. Exactly + the same situation as single column output exists.) + GNU pr gives priority to the 2nd requirement and observes POSIX + column definition. The n-separator TAB is expanded to the same number + of spaces in each column using the default value 8. Tabification is + only performed if it is compatible with the output position. + Consequence: The output text columns are of equal width. The layout + of a page does not change if the left margin varies. - Looks better + than the SunOS approach. + SunOS pr gives priority to the 1st requirement. n-separator TAB + width varies with each column. Only the width of text part of the + column is fixed. + Consequence: The output text columns don't have equal width. The + widths and the layout of the whole page varies with the left margin. + An overflow of the line length (without margin) over the input value + PAGE_WIDTH may occur. + + The interference of the POSIX-compliant small letter options -w and -s: + ("interference" means "setting a _separator_ with -s switches off the + column structure and the default - not generally - page_width, + acts on -w option") + options: text form / separator: equivalent new options: + -w l -s[x] + -------------------------------------------------------------------- + 1. -- -- columns / space -- + trunc. to page_width = 72 + 2. -- -s[:] full lines / TAB[:] -J --sep-string[="<TAB>"|:] + no truncation + 3. -w l -- columns / space -W l + trunc. to page_width = l + 4. -w l -s[:] columns / no sep.[:] -W l --sep-string[=:] + trunc. to page_width = l + -------------------------------------------------------------------- + + + Options: + + Including version 1.22i: + Some SMALL LETTER options have been redefined with the object of a + better POSIX compliance. The output of some further cases has been + adapted to other UNIXes. A violation of downward compatibility has to + be accepted. + Some NEW CAPITAL LETTER options ( -J, -S, -W) has been introduced to + turn off unexpected interferences of small letter options (-s and -w + together with the three column options). + -N option and the second argument LAST_PAGE of +FIRST_PAGE offer more + flexibility; The detailed handling of form feeds set in the input + files requires -T option. + + Capital letter options dominate small letter ones. + + Some of the option-arguments cannot be specified as separate arguments + from the preceding option letter (already stated in POSIX specification). + + Form feeds in the input cause page breaks in the output. Multiple + form feeds produce empty pages. + + +FIRST_PAGE[:LAST_PAGE], --pages=FIRST_PAGE[:LAST_PAGE] + begin [stop] printing with page FIRST_[LAST_]PAGE + + -COLUMN, --columns=COLUMN + Produce output that is COLUMN columns wide and + print columns down, unless -a is used. Balance number of + lines in the columns on each page. + + -a, --across Print columns across rather than down, used + together with -COLUMN. The input + one + two + three + four + will be printed with '-a -3' as + one two three + four + + -b Balance columns on the last page. + -b is no longer an independent option. It's always used + together with -COLUMN (unless -a is used) to get a + consistent formulation with "FF set by hand" in input + files. Each formfeed found terminates the number of lines + to be read with the actual page. The situation for + printing columns down is equivalent to that on the last + page. So we need a balancing. + + Keeping -b as an underground option guarantees some + downward compatibility. Utilities using pr with -b + (a most frequently used form) still work as usual. + + -c, --show-control-chars + Print unprintable characters as control prefixes. + Control-g is printed as ^G (use hat notation) and + octal backslash notation. + + -d, --double-space Double space the output. + + -D FORMAT, --date-format=FORMAT Use FORMAT for the header date. + + -e[CHAR[WIDTH]], --expand-tabs[=CHAR[WIDTH]] + Expand tabs to spaces on input. Optional argument CHAR + is the input TAB character. (Default is TAB). Optional + argument WIDTH is the input TAB character's width. + (Default is 8.) + + -F, -f, --form-feed Use formfeeds instead of newlines to separate + pages. A three line HEADER is used, no TRAILER with -F, + without -F both HEADER and TRAILER are made of five lines. + + -h HEADER, --header=HEADER + Replace the filename in the header with the string HEADER. + A centered header is used. + + -i[CHAR[WIDTH]], --output-tabs[=CHAR[WIDTH]] + Replace spaces with tabs on output. Optional argument + CHAR is the output TAB character. (Default is TAB). + Optional argument WIDTH is the output TAB character's + width. (Default is 8) + + -J, --join-lines Merge lines of full length, turns off -W/-w + line truncation, no column alignment, --sep-string[=STRING] + sets separators, works with all column options + (-COLUMN | -a -COLUMN | -m). + -J has been introduced (together with -W and --sep-string) to + disentangle the old (POSIX compliant) options -w, -s + along with the 3 column options. + + -l PAGE_LENGTH, --length=PAGE_LENGTH + Set the page length to PAGE_LENGTH lines. Default is 66, + including 5 lines of HEADER and 5 lines of TRAILER + without -F, but only 3 lines of HEADER and no TRAILER + with -F (i.e the number of text lines defaults to 56 or + 63 respectively). + + -m, --merge Print files in parallel; pad_across_to align + columns; truncate lines and print separator strings; + Do it also with empty columns to get a continuous line + numbering and column marking by separators throughout + the whole merged file. + + Empty pages in some input files produce empty columns + [marked by separators] in the merged pages. Completely + empty merged pages show no column separators at all. + + The layout of a merged page is ruled by the largest form + feed distance of the single pages at that page. Shorter + columns will be filled up with empty lines. + + Together with -J option join lines of full length and + set separators when -S option is used. + + -n[SEP[DIGITS]], --number-lines[=SEP[DIGITS]] + Provide DIGITS digit line numbering (default for DIGITS + is 5). With multicolumn output the number occupies the + first DIGITS column positions of each text column or only + each line of -m output. + With single column output the number precedes each line + just as -m output. + Optional argument SEP is the character appended to the + line number to separate it from the text followed. + The default separator is a TAB. In a strict sense a TAB + is always printed with single column output only. The + TAB-width varies with the TAB-position, e.g. with the + left margin specified by -o option. + With multicolumn output priority is given to "equal width + of output columns" (a POSIX specification). The TAB-width + is fixed to the value of the 1st column and does not + change with different values of left margin. That means a + fixed number of spaces is always printed in the place of + a TAB. The tabification depends upon the output + position. + + Default counting of the line numbers starts with 1st + line of the input file (not the 1st line printed, + compare the --page option and -N option). + + -N NUMBER, --first-line-number=NUMBER + Start line counting with the number NUMBER at the 1st + line of first page printed (mostly not the 1st line of + the input file). + + -o MARGIN, --indent=MARGIN + Offset each line with a margin MARGIN spaces wide. + Total page width is the size of the margin plus the + PAGE_WIDTH set with -W/-w option. + + -r, --no-file-warnings + Omit warning when a file cannot be opened. + + -s[CHAR], --separator[=CHAR] + Separate columns by a single character CHAR, default for + CHAR is the TAB character without -w and 'no char' with -w. + Without '-s' default separator 'space' is set. + -s[CHAR] turns off line truncation of all 3 column options + (-COLUMN|-a -COLUMN|-m) except -w is set. That is a POSIX + compliant formulation. The source code translates -s into + the new options -S and -J, also -W if required. + + -S[STRING], --sep-string[=STRING] + Separate columns by any string STRING. The -S option + doesn't react upon the -W/-w option (unlike -s option + does). It defines a separator nothing else. + Without -S: Default separator TAB is used with -J and + 'space' otherwise (same as -S" "). + With -S "": No separator is used. + Quotes should be used with blanks and some shell active + characters. + -S is problematic because in its obsolete form you + cannot use -S "STRING", but in its standard form you + must use -S "STRING" if STRING is empty. Use + --sep-string to avoid the ambiguity. + + -t, --omit-header Do not print headers or footers but retain form + feeds set in the input files. + + -T, --omit-pagination + Do not print headers or footers, eliminate any pagination + by form feeds set in the input files. + + -v, --show-nonprinting + Print unprintable characters as escape sequences. Use + octal backslash notation. Control-G becomes \007. + + -w PAGE_WIDTH, --width=PAGE_WIDTH + Set page width to PAGE_WIDTH characters for multiple + text-column output only (default for PAGE_WIDTH is 72). + -s[CHAR] turns off the default page width and any line + truncation. Lines of full length will be merged, + regardless of the column options set. A POSIX compliant + formulation. + + -W PAGE_WIDTH, --page-width=PAGE_WIDTH + Set the page width to PAGE_WIDTH characters. That's valid + with and without a column option. Text lines will be + truncated, unless -J is used. Together with one of the + column options (-COLUMN| -a -COLUMN| -m) column alignment + is always used. + Default is 72 characters. + Without -W PAGE_WIDTH + - but with one of the column options default truncation of + 72 characters is used (to keep downward compatibility + and to simplify most frequently met column tasks). + Column alignment and column separators are used. + - and without any of the column options NO line truncation + is used (to keep downward compatibility and to meet most + frequent tasks). That's equivalent to -W 72 -J . + + With/without -W PAGE_WIDTH the header line is always + truncated to avoid line overflow. + + (In pr versions newer than 1.14 -S option does no longer + affect -W option.) + +*/ + +#include <config.h> + +#include <getopt.h> +#include <sys/types.h> +#include "system.h" +#include "die.h" +#include "error.h" +#include "fadvise.h" +#include "hard-locale.h" +#include "mbswidth.h" +#include "quote.h" +#include "stat-time.h" +#include "stdio--.h" +#include "strftime.h" +#include "xstrtol.h" +#include "xstrtol-error.h" +#include "xdectoint.h" + +/* The official name of this program (e.g., no 'g' prefix). */ +#define PROGRAM_NAME "pr" + +#define AUTHORS \ + proper_name ("Pete TerMaat"), \ + proper_name ("Roland Huebner") + +/* Used with start_position in the struct COLUMN described below. + If start_position == ANYWHERE, we aren't truncating columns and + can begin printing a column anywhere. Otherwise we must pad to + the horizontal position start_position. */ +#define ANYWHERE 0 + +/* Each column has one of these structures allocated for it. + If we're only dealing with one file, fp is the same for all + columns. + + The general strategy is to spend time setting up these column + structures (storing columns if necessary), after which printing + is a matter of flitting from column to column and calling + print_func. + + Parallel files, single files printing across in multiple + columns, and single files printing down in multiple columns all + fit the same printing loop. + + print_func Function used to print lines in this column. + If we're storing this column it will be + print_stored(), Otherwise it will be read_line(). + + char_func Function used to process characters in this column. + If we're storing this column it will be store_char(), + otherwise it will be print_char(). + + current_line Index of the current entry in line_vector, which + contains the index of the first character of the + current line in buff[]. + + lines_stored Number of lines in this column which are stored in + buff. + + lines_to_print If we're storing this column, lines_to_print is + the number of stored_lines which remain to be + printed. Otherwise it is the number of lines + we can print without exceeding lines_per_body. + + start_position The horizontal position we want to be in before we + print the first character in this column. + + numbered True means precede this column with a line number. */ + +/* FIXME: There are many unchecked integer overflows in this file, + that will cause this command to misbehave given large inputs or + options. Many of the "int" values below should be "size_t" or + something else like that. */ + +struct COLUMN; +struct COLUMN + { + FILE *fp; /* Input stream for this column. */ + char const *name; /* File name. */ + enum + { + OPEN, + FF_FOUND, /* used with -b option, set with \f, changed + to ON_HOLD after print_header */ + ON_HOLD, /* Hit a form feed. */ + CLOSED + } + status; /* Status of the file pointer. */ + + /* Func to print lines in this col. */ + bool (*print_func) (struct COLUMN *); + + /* Func to print/store chars in this col. */ + void (*char_func) (char); + + int current_line; /* Index of current place in line_vector. */ + int lines_stored; /* Number of lines stored in buff. */ + int lines_to_print; /* No. lines stored or space left on page. */ + int start_position; /* Horizontal position of first char. */ + bool numbered; + bool full_page_printed; /* True means printed without a FF found. */ + + /* p->full_page_printed controls a special case of "FF set by hand": + True means a full page has been printed without FF found. To avoid an + additional empty page we have to ignore a FF immediately following in + the next line. */ + }; + +typedef struct COLUMN COLUMN; + +static int char_to_clump (char c); +static bool read_line (COLUMN *p); +static bool print_page (void); +static bool print_stored (COLUMN *p); +static bool open_file (char *name, COLUMN *p); +static bool skip_to_page (uintmax_t page); +static void print_header (void); +static void pad_across_to (int position); +static void add_line_number (COLUMN *p); +static void getoptnum (char const *n_str, int min, int *num, + char const *errfmt); +static void getoptarg (char *arg, char switch_char, char *character, + int *number); +static void print_files (int number_of_files, char **av); +static void init_parameters (int number_of_files); +static void init_header (char const *filename, int desc); +static bool init_fps (int number_of_files, char **av); +static void init_funcs (void); +static void init_store_cols (void); +static void store_columns (void); +static void balance (int total_stored); +static void store_char (char c); +static void pad_down (unsigned int lines); +static void read_rest_of_line (COLUMN *p); +static void skip_read (COLUMN *p, int column_number); +static void print_char (char c); +static void cleanup (void); +static void print_sep_string (void); +static void separator_string (char const *optarg_S); + +/* All of the columns to print. */ +static COLUMN *column_vector; + +/* When printing a single file in multiple downward columns, + we store the leftmost columns contiguously in buff. + To print a line from buff, get the index of the first character + from line_vector[i], and print up to line_vector[i + 1]. */ +static char *buff; + +/* Index of the position in buff where the next character + will be stored. */ +static unsigned int buff_current; + +/* The number of characters in buff. + Used for allocation of buff and to detect overflow of buff. */ +static size_t buff_allocated; + +/* Array of indices into buff. + Each entry is an index of the first character of a line. + This is used when storing lines to facilitate shuffling when + we do column balancing on the last page. */ +static int *line_vector; + +/* Array of horizontal positions. + For each line in line_vector, end_vector[line] is the horizontal + position we are in after printing that line. We keep track of this + so that we know how much we need to pad to prepare for the next + column. */ +static int *end_vector; + +/* (-m) True means we're printing multiple files in parallel. */ +static bool parallel_files = false; + +/* (-m) True means a line starts with some empty columns (some files + already CLOSED or ON_HOLD) which we have to align. */ +static bool align_empty_cols; + +/* (-m) True means we have not yet found any printable column in a line. + align_empty_cols = true has to be maintained. */ +static bool empty_line; + +/* (-m) False means printable column output precedes a form feed found. + Column alignment is done only once. No additional action with that form + feed. + True means we found only a form feed in a column. Maybe we have to do + some column alignment with that form feed. */ +static bool FF_only; + +/* (-[0-9]+) True means we're given an option explicitly specifying + number of columns. Used to detect when this option is used with -m + and when translating old options to new/long options. */ +static bool explicit_columns = false; + +/* (-t|-T) False means we aren't printing headers and footers. */ +static bool extremities = true; + +/* (-t) True means we retain all FF set by hand in input files. + False is set with -T option. */ +static bool keep_FF = false; +static bool print_a_FF = false; + +/* True means we need to print a header as soon as we know we've got input + to print after it. */ +static bool print_a_header; + +/* (-f) True means use formfeeds instead of newlines to separate pages. */ +static bool use_form_feed = false; + +/* True means we have read the standard input. */ +static bool have_read_stdin = false; + +/* True means the -a flag has been given. */ +static bool print_across_flag = false; + +/* True means we're printing one file in multiple (>1) downward columns. */ +static bool storing_columns = true; + +/* (-b) True means balance columns on the last page as Sys V does. */ +/* That's no longer an independent option. With storing_columns = true + balance_columns = true is used too (s. function init_parameters). + We get a consistent formulation with "FF set by hand" in input files. */ +static bool balance_columns = false; + +/* (-l) Number of lines on a page, including header and footer lines. */ +static int lines_per_page = 66; + +/* Number of lines in the header and footer can be reset to 0 using + the -t flag. */ +enum { lines_per_header = 5 }; +static int lines_per_body; +enum { lines_per_footer = 5 }; + +/* (-w|-W) Width in characters of the page. Does not include the width of + the margin. */ +static int chars_per_line = 72; + +/* (-w|W) True means we truncate lines longer than chars_per_column. */ +static bool truncate_lines = false; + +/* (-J) True means we join lines without any line truncation. -J + dominates -w option. */ +static bool join_lines = false; + +/* Number of characters in a column. Based on col_sep_length and + page width. */ +static int chars_per_column; + +/* (-e) True means convert tabs to spaces on input. */ +static bool untabify_input = false; + +/* (-e) The input tab character. */ +static char input_tab_char = '\t'; + +/* (-e) Tabstops are at chars_per_tab, 2*chars_per_tab, 3*chars_per_tab, ... + where the leftmost column is 1. */ +static int chars_per_input_tab = 8; + +/* (-i) True means convert spaces to tabs on output. */ +static bool tabify_output = false; + +/* (-i) The output tab character. */ +static char output_tab_char = '\t'; + +/* (-i) The width of the output tab. */ +static int chars_per_output_tab = 8; + +/* Keeps track of pending white space. When we hit a nonspace + character after some whitespace, we print whitespace, tabbing + if necessary to get to output_position + spaces_not_printed. */ +static int spaces_not_printed; + +/* (-o) Number of spaces in the left margin (tabs used when possible). */ +static int chars_per_margin = 0; + +/* Position where the next character will fall. + Leftmost position is 0 + chars_per_margin. + Rightmost position is chars_per_margin + chars_per_line - 1. + This is important for converting spaces to tabs on output. */ +static int output_position; + +/* Horizontal position relative to the current file. + (output_position depends on where we are on the page; + input_position depends on where we are in the file.) + Important for converting tabs to spaces on input. */ +static int input_position; + +/* True if there were any failed opens so we can exit with nonzero + status. */ +static bool failed_opens = false; + +/* The number of spaces taken up if we print a tab character with width + c_ from position h_. */ +#define TAB_WIDTH(c_, h_) ((c_) - ((h_) % (c_))) + +/* The horizontal position we'll be at after printing a tab character + of width c_ from the position h_. */ +#define POS_AFTER_TAB(c_, h_) ((h_) + TAB_WIDTH (c_, h_)) + +/* (-NNN) Number of columns of text to print. */ +static int columns = 1; + +/* (+NNN:MMM) Page numbers on which to begin and stop printing. + first_page_number = 0 will be used to check input only. */ +static uintmax_t first_page_number = 0; +static uintmax_t last_page_number = UINTMAX_MAX; + +/* Number of files open (not closed, not on hold). */ +static int files_ready_to_read = 0; + +/* Current page number. Displayed in header. */ +static uintmax_t page_number; + +/* Current line number. Displayed when -n flag is specified. + + When printing files in parallel (-m flag), line numbering is as follows: + 1 foo goo moo + 2 hoo too zoo + + When printing files across (-a flag), ... + 1 foo 2 moo 3 goo + 4 hoo 5 too 6 zoo + + Otherwise, line numbering is as follows: + 1 foo 3 goo 5 too + 2 moo 4 hoo 6 zoo */ +static int line_number; + +/* (-n) True means lines should be preceded by numbers. */ +static bool numbered_lines = false; + +/* (-n) Character which follows each line number. */ +static char number_separator = '\t'; + +/* (-n) line counting starts with 1st line of input file (not with 1st + line of 1st page printed). */ +static int line_count = 1; + +/* (-n) True means counting of skipped lines starts with 1st line of + input file. False means -N option is used in addition, counting of + skipped lines not required. */ +static bool skip_count = true; + +/* (-N) Counting starts with start_line_number = NUMBER at 1st line of + first page printed, usually not 1st page of input file. */ +static int start_line_num = 1; + +/* (-n) Width in characters of a line number. */ +static int chars_per_number = 5; + +/* Used when widening the first column to accommodate numbers -- only + needed when printing files in parallel. Includes width of both the + number and the number_separator. */ +static int number_width; + +/* Buffer sprintf uses to format a line number. */ +static char *number_buff; + +/* (-v) True means unprintable characters are printed as escape sequences. + control-g becomes \007. */ +static bool use_esc_sequence = false; + +/* (-c) True means unprintable characters are printed as control prefixes. + control-g becomes ^G. */ +static bool use_cntrl_prefix = false; + +/* (-d) True means output is double spaced. */ +static bool double_space = false; + +/* Number of files opened initially in init_files. Should be 1 + unless we're printing multiple files in parallel. */ +static int total_files = 0; + +/* (-r) True means don't complain if we can't open a file. */ +static bool ignore_failed_opens = false; + +/* (-S) True means we separate columns with a specified string. + -S option does not affect line truncation nor column alignment. */ +static bool use_col_separator = false; + +/* String used to separate columns if the -S option has been specified. + Default without -S but together with one of the column options + -a|COLUMN|-m is a 'space' and with the -J option a 'tab'. */ +static char const *col_sep_string = ""; +static int col_sep_length = 0; +static char *column_separator = (char *) " "; +static char *line_separator = (char *) "\t"; + +/* Number of separator characters waiting to be printed as soon as we + know that we have any input remaining to be printed. */ +static int separators_not_printed; + +/* Position we need to pad to, as soon as we know that we have input + remaining to be printed. */ +static int padding_not_printed; + +/* True means we should pad the end of the page. Remains false until we + know we have a page to print. */ +static bool pad_vertically; + +/* (-h) String of characters used in place of the filename in the header. */ +static char *custom_header; + +/* (-D) Date format for the header. */ +static char const *date_format; + +/* The local time zone rules, as per the TZ environment variable. */ +static timezone_t localtz; + +/* Date and file name for the header. */ +static char *date_text; +static char const *file_text; + +/* Output columns available, not counting the date and file name. */ +static int header_width_available; + +static char *clump_buff; + +/* True means we read the line no. lines_per_body in skip_read + called by skip_to_page. That variable controls the coincidence of a + "FF set by hand" and "full_page_printed", see above the definition of + structure COLUMN. */ +static bool last_line = false; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + COLUMNS_OPTION = CHAR_MAX + 1, + PAGES_OPTION +}; + +static char const short_options[] = + "-0123456789D:FJN:S::TW:abcde::fh:i::l:mn::o:rs::tvw:"; + +static struct option const long_options[] = +{ + {"pages", required_argument, NULL, PAGES_OPTION}, + {"columns", required_argument, NULL, COLUMNS_OPTION}, + {"across", no_argument, NULL, 'a'}, + {"show-control-chars", no_argument, NULL, 'c'}, + {"double-space", no_argument, NULL, 'd'}, + {"date-format", required_argument, NULL, 'D'}, + {"expand-tabs", optional_argument, NULL, 'e'}, + {"form-feed", no_argument, NULL, 'f'}, + {"header", required_argument, NULL, 'h'}, + {"output-tabs", optional_argument, NULL, 'i'}, + {"join-lines", no_argument, NULL, 'J'}, + {"length", required_argument, NULL, 'l'}, + {"merge", no_argument, NULL, 'm'}, + {"number-lines", optional_argument, NULL, 'n'}, + {"first-line-number", required_argument, NULL, 'N'}, + {"indent", required_argument, NULL, 'o'}, + {"no-file-warnings", no_argument, NULL, 'r'}, + {"separator", optional_argument, NULL, 's'}, + {"sep-string", optional_argument, NULL, 'S'}, + {"omit-header", no_argument, NULL, 't'}, + {"omit-pagination", no_argument, NULL, 'T'}, + {"show-nonprinting", no_argument, NULL, 'v'}, + {"width", required_argument, NULL, 'w'}, + {"page-width", required_argument, NULL, 'W'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static void +integer_overflow (void) +{ + die (EXIT_FAILURE, 0, _("integer overflow")); +} + +/* Return the number of columns that have either an open file or + stored lines. */ + +ATTRIBUTE_PURE +static unsigned int +cols_ready_to_print (void) +{ + COLUMN *q; + unsigned int i; + unsigned int n; + + n = 0; + for (q = column_vector, i = 0; i < columns; ++q, ++i) + if (q->status == OPEN + || q->status == FF_FOUND /* With -b: To print a header only */ + || (storing_columns && q->lines_stored > 0 && q->lines_to_print > 0)) + ++n; + return n; +} + +/* Estimate first_ / last_page_number + using option +FIRST_PAGE:LAST_PAGE */ + +static bool +first_last_page (int oi, char c, char const *pages) +{ + char *p; + uintmax_t first; + uintmax_t last = UINTMAX_MAX; + strtol_error err = xstrtoumax (pages, &p, 10, &first, ""); + if (err != LONGINT_OK && err != LONGINT_INVALID_SUFFIX_CHAR) + xstrtol_fatal (err, oi, c, long_options, pages); + + if (p == pages || !first) + return false; + + if (*p == ':') + { + char const *p1 = p + 1; + err = xstrtoumax (p1, &p, 10, &last, ""); + if (err != LONGINT_OK) + xstrtol_fatal (err, oi, c, long_options, pages); + if (p1 == p || last < first) + return false; + } + + if (*p) + return false; + + first_page_number = first; + last_page_number = last; + return true; +} + +/* Parse column count string S, and if it's valid (1 or larger and + within range of the type of 'columns') set the global variables + columns and explicit_columns. Otherwise, exit with a diagnostic. */ + +static void +parse_column_count (char const *s) +{ + getoptnum (s, 1, &columns, _("invalid number of columns")); + explicit_columns = true; +} + +/* Estimate length of col_sep_string with option -S. */ + +static void +separator_string (char const *optarg_S) +{ + size_t len = strlen (optarg_S); + if (INT_MAX < len) + integer_overflow (); + col_sep_length = len; + col_sep_string = optarg_S; +} + +int +main (int argc, char **argv) +{ + unsigned int n_files; + bool old_options = false; + bool old_w = false; + bool old_s = false; + char **file_names; + + /* Accumulate the digits of old-style options like -99. */ + char *column_count_string = NULL; + size_t n_digits = 0; + size_t n_alloc = 0; + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + n_files = 0; + file_names = (argc > 1 + ? xnmalloc (argc - 1, sizeof (char *)) + : NULL); + + while (true) + { + int oi = -1; + int c = getopt_long (argc, argv, short_options, long_options, &oi); + if (c == -1) + break; + + if (ISDIGIT (c)) + { + /* Accumulate column-count digits specified via old-style options. */ + if (n_digits + 1 >= n_alloc) + column_count_string + = X2REALLOC (column_count_string, &n_alloc); + column_count_string[n_digits++] = c; + column_count_string[n_digits] = '\0'; + continue; + } + + n_digits = 0; + + switch (c) + { + case 1: /* Non-option argument. */ + /* long option --page dominates old '+FIRST_PAGE ...'. */ + if (! (first_page_number == 0 + && *optarg == '+' && first_last_page (-2, '+', optarg + 1))) + file_names[n_files++] = optarg; + break; + + case PAGES_OPTION: /* --pages=FIRST_PAGE[:LAST_PAGE] */ + { /* dominates old opt +... */ + if (! optarg) + die (EXIT_FAILURE, 0, + _("'--pages=FIRST_PAGE[:LAST_PAGE]' missing argument")); + else if (! first_last_page (oi, 0, optarg)) + die (EXIT_FAILURE, 0, _("invalid page range %s"), + quote (optarg)); + break; + } + + case COLUMNS_OPTION: /* --columns=COLUMN */ + { + parse_column_count (optarg); + + /* If there was a prior column count specified via the + short-named option syntax, e.g., -9, ensure that this + long-name-specified value overrides it. */ + free (column_count_string); + column_count_string = NULL; + n_alloc = 0; + break; + } + + case 'a': + print_across_flag = true; + storing_columns = false; + break; + case 'b': + balance_columns = true; + break; + case 'c': + use_cntrl_prefix = true; + break; + case 'd': + double_space = true; + break; + case 'D': + date_format = optarg; + break; + case 'e': + if (optarg) + getoptarg (optarg, 'e', &input_tab_char, + &chars_per_input_tab); + /* Could check tab width > 0. */ + untabify_input = true; + break; + case 'f': + case 'F': + use_form_feed = true; + break; + case 'h': + custom_header = optarg; + break; + case 'i': + if (optarg) + getoptarg (optarg, 'i', &output_tab_char, + &chars_per_output_tab); + /* Could check tab width > 0. */ + tabify_output = true; + break; + case 'J': + join_lines = true; + break; + case 'l': + getoptnum (optarg, 1, &lines_per_page, + _("'-l PAGE_LENGTH' invalid number of lines")); + break; + case 'm': + parallel_files = true; + storing_columns = false; + break; + case 'n': + numbered_lines = true; + if (optarg) + getoptarg (optarg, 'n', &number_separator, + &chars_per_number); + break; + case 'N': + skip_count = false; + getoptnum (optarg, INT_MIN, &start_line_num, + _("'-N NUMBER' invalid starting line number")); + break; + case 'o': + getoptnum (optarg, 0, &chars_per_margin, + _("'-o MARGIN' invalid line offset")); + break; + case 'r': + ignore_failed_opens = true; + break; + case 's': + old_options = true; + old_s = true; + if (!use_col_separator && optarg) + separator_string (optarg); + break; + case 'S': + old_s = false; + /* Reset an additional input of -s, -S dominates -s */ + col_sep_string = ""; + col_sep_length = 0; + use_col_separator = true; + if (optarg) + separator_string (optarg); + break; + case 't': + extremities = false; + keep_FF = true; + break; + case 'T': + extremities = false; + keep_FF = false; + break; + case 'v': + use_esc_sequence = true; + break; + case 'w': + old_options = true; + old_w = true; + { + int tmp_cpl; + getoptnum (optarg, 1, &tmp_cpl, + _("'-w PAGE_WIDTH' invalid number of characters")); + if (! truncate_lines) + chars_per_line = tmp_cpl; + } + break; + case 'W': + old_w = false; /* dominates -w */ + truncate_lines = true; + getoptnum (optarg, 1, &chars_per_line, + _("'-W PAGE_WIDTH' invalid number of characters")); + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + break; + } + } + + if (column_count_string) + { + parse_column_count (column_count_string); + free (column_count_string); + } + + if (! date_format) + date_format = (getenv ("POSIXLY_CORRECT") && !hard_locale (LC_TIME) + ? "%b %e %H:%M %Y" + : "%Y-%m-%d %H:%M"); + + localtz = tzalloc (getenv ("TZ")); + + /* Now we can set a reasonable initial value: */ + if (first_page_number == 0) + first_page_number = 1; + + if (parallel_files && explicit_columns) + die (EXIT_FAILURE, 0, + _("cannot specify number of columns when printing in parallel")); + + if (parallel_files && print_across_flag) + die (EXIT_FAILURE, 0, + _("cannot specify both printing across and printing in parallel")); + +/* Translate some old short options to new/long options. + To meet downward compatibility with other UNIX pr utilities + and some POSIX specifications. */ + + if (old_options) + { + if (old_w) + { + if (parallel_files || explicit_columns) + { + /* activate -W */ + truncate_lines = true; + if (old_s) + /* adapt HP-UX and SunOS: -s = no separator; + activate -S */ + use_col_separator = true; + } + else + /* old -w sets width with columns only + activate -J */ + join_lines = true; + } + else if (!use_col_separator) + { + /* No -S option read */ + if (old_s && (parallel_files || explicit_columns)) + { + if (!truncate_lines) + { + /* old -s (without -w and -W) annuls column alignment, + uses fields, activate -J */ + join_lines = true; + if (col_sep_length > 0) + /* activate -S */ + use_col_separator = true; + } + else + /* with -W */ + /* adapt HP-UX and SunOS: -s = no separator; + activate -S */ + use_col_separator = true; + } + } + } + + for (; optind < argc; optind++) + { + file_names[n_files++] = argv[optind]; + } + + if (n_files == 0) + { + /* No file arguments specified; read from standard input. */ + print_files (0, NULL); + } + else + { + if (parallel_files) + print_files (n_files, file_names); + else + { + for (unsigned int i = 0; i < n_files; i++) + print_files (1, &file_names[i]); + } + } + + cleanup (); + + if (have_read_stdin && fclose (stdin) == EOF) + die (EXIT_FAILURE, errno, _("standard input")); + main_exit (failed_opens ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/* Parse numeric arguments, ensuring MIN <= number <= INT_MAX. */ + +static void +getoptnum (char const *n_str, int min, int *num, char const *err) +{ + intmax_t tnum = xdectoimax (n_str, min, INT_MAX, "", err, 0); + *num = tnum; +} + +/* Parse options of the form -scNNN. + + Example: -nck, where 'n' is the option, c is the optional number + separator, and k is the optional width of the field used when printing + a number. */ + +static void +getoptarg (char *arg, char switch_char, char *character, int *number) +{ + if (!ISDIGIT (*arg)) + *character = *arg++; + if (*arg) + { + long int tmp_long; + if (xstrtol (arg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long <= 0 || INT_MAX < tmp_long) + { + error (0, INT_MAX < tmp_long ? EOVERFLOW : errno, + _("'-%c' extra characters or invalid number in the argument: %s"), + switch_char, quote (arg)); + usage (EXIT_FAILURE); + } + *number = tmp_long; + } +} + +/* Set parameters related to formatting. */ + +static void +init_parameters (int number_of_files) +{ + int chars_used_by_number = 0; + + lines_per_body = lines_per_page - lines_per_header - lines_per_footer; + if (lines_per_body <= 0) + { + extremities = false; + keep_FF = true; + } + if (extremities == false) + lines_per_body = lines_per_page; + + if (double_space) + lines_per_body = lines_per_body / 2; + + /* If input is stdin, cannot print parallel files. BSD dumps core + on this. */ + if (number_of_files == 0) + parallel_files = false; + + if (parallel_files) + columns = number_of_files; + + /* One file, multi columns down: -b option is set to get a consistent + formulation with "FF set by hand" in input files. */ + if (storing_columns) + balance_columns = true; + + /* Tabification is assumed for multiple columns. */ + if (columns > 1) + { + if (!use_col_separator) + { + /* Use default separator */ + if (join_lines) + col_sep_string = line_separator; + else + col_sep_string = column_separator; + + col_sep_length = 1; + use_col_separator = true; + } + /* It's rather pointless to define a TAB separator with column + alignment */ + else if (!join_lines && col_sep_length == 1 && *col_sep_string == '\t') + col_sep_string = column_separator; + + truncate_lines = true; + if (! (col_sep_length == 1 && *col_sep_string == '\t')) + untabify_input = true; + tabify_output = true; + } + else + storing_columns = false; + + /* -J dominates -w in any case */ + if (join_lines) + truncate_lines = false; + + if (numbered_lines) + { + int chars_per_default_tab = 8; + + line_count = start_line_num; + + /* To allow input tab-expansion (-e sensitive) use: + if (number_separator == input_tab_char) + number_width = chars_per_number + + TAB_WIDTH (chars_per_input_tab, chars_per_number); */ + + /* Estimate chars_per_text without any margin and keep it constant. */ + if (number_separator == '\t') + number_width = (chars_per_number + + TAB_WIDTH (chars_per_default_tab, chars_per_number)); + else + number_width = chars_per_number + 1; + + /* The number is part of the column width unless we are + printing files in parallel. */ + if (parallel_files) + chars_used_by_number = number_width; + } + + int sep_chars, useful_chars; + if (INT_MULTIPLY_WRAPV (columns - 1, col_sep_length, &sep_chars)) + sep_chars = INT_MAX; + if (INT_SUBTRACT_WRAPV (chars_per_line - chars_used_by_number, sep_chars, + &useful_chars)) + useful_chars = 0; + chars_per_column = useful_chars / columns; + + if (chars_per_column < 1) + die (EXIT_FAILURE, 0, _("page width too narrow")); + + if (numbered_lines) + { + free (number_buff); + number_buff = xmalloc (MAX (chars_per_number, + INT_STRLEN_BOUND (line_number)) + 1); + } + + /* Pick the maximum between the tab width and the width of an + escape sequence. + The width of an escape sequence (4) isn't the lower limit any longer. + We've to use 8 as the lower limit, if we use chars_per_default_tab = 8 + to expand a tab which is not an input_tab-char. */ + free (clump_buff); + clump_buff = xmalloc (MAX (8, chars_per_input_tab)); +} + +/* Open the necessary files, + maintaining a COLUMN structure for each column. + + With multiple files, each column p has a different p->fp. + With single files, each column p has the same p->fp. + Return false if (number_of_files > 0) and no files can be opened, + true otherwise. + + With each column/file p, p->full_page_printed is initialized, + see also open_file. */ + +static bool +init_fps (int number_of_files, char **av) +{ + COLUMN *p; + + total_files = 0; + + free (column_vector); + column_vector = xnmalloc (columns, sizeof (COLUMN)); + + if (parallel_files) + { + int files_left = number_of_files; + for (p = column_vector; files_left--; ++p, ++av) + { + if (! open_file (*av, p)) + { + --p; + --columns; + } + } + if (columns == 0) + return false; + init_header ("", -1); + } + else + { + p = column_vector; + if (number_of_files > 0) + { + if (! open_file (*av, p)) + return false; + init_header (*av, fileno (p->fp)); + p->lines_stored = 0; + } + else + { + p->name = _("standard input"); + p->fp = stdin; + have_read_stdin = true; + p->status = OPEN; + p->full_page_printed = false; + ++total_files; + init_header ("", -1); + p->lines_stored = 0; + } + + char const *firstname = p->name; + FILE *firstfp = p->fp; + int i; + for (i = columns - 1, ++p; i; --i, ++p) + { + p->name = firstname; + p->fp = firstfp; + p->status = OPEN; + p->full_page_printed = false; + p->lines_stored = 0; + } + } + files_ready_to_read = total_files; + return true; +} + +/* Determine print_func and char_func, the functions + used by each column for printing and/or storing. + + Determine the horizontal position desired when we begin + printing a column (p->start_position). */ + +static void +init_funcs (void) +{ + int i, h, h_next; + COLUMN *p; + + h = chars_per_margin; + + if (!truncate_lines) + h_next = ANYWHERE; + else + { + /* When numbering lines of parallel files, we enlarge the + first column to accommodate the number. Looks better than + the Sys V approach. */ + if (parallel_files && numbered_lines) + h_next = h + chars_per_column + number_width; + else + h_next = h + chars_per_column; + } + + /* Enlarge p->start_position of first column to use the same form of + padding_not_printed with all columns. */ + h = h + col_sep_length; + + /* This loop takes care of all but the rightmost column. */ + + for (p = column_vector, i = 1; i < columns; ++p, ++i) + { + if (storing_columns) /* One file, multi columns down. */ + { + p->char_func = store_char; + p->print_func = print_stored; + } + else + /* One file, multi columns across; or parallel files. */ + { + p->char_func = print_char; + p->print_func = read_line; + } + + /* Number only the first column when printing files in + parallel. */ + p->numbered = numbered_lines && (!parallel_files || i == 1); + p->start_position = h; + + /* If we don't truncate lines, all start_positions are + ANYWHERE, except the first column's start_position when + using a margin. */ + + if (!truncate_lines) + { + h = ANYWHERE; + h_next = ANYWHERE; + } + else + { + h = h_next + col_sep_length; + h_next = h + chars_per_column; + } + } + + /* The rightmost column. + + Doesn't need to be stored unless we intend to balance + columns on the last page. */ + if (storing_columns && balance_columns) + { + p->char_func = store_char; + p->print_func = print_stored; + } + else + { + p->char_func = print_char; + p->print_func = read_line; + } + + p->numbered = numbered_lines && (!parallel_files || i == 1); + p->start_position = h; +} + +/* Open a file. Return true if successful. + + With each file p, p->full_page_printed is initialized, + see also init_fps. */ + +static bool +open_file (char *name, COLUMN *p) +{ + if (STREQ (name, "-")) + { + p->name = _("standard input"); + p->fp = stdin; + have_read_stdin = true; + } + else + { + p->name = name; + p->fp = fopen (name, "r"); + } + if (p->fp == NULL) + { + failed_opens = true; + if (!ignore_failed_opens) + error (0, errno, "%s", quotef (name)); + return false; + } + fadvise (p->fp, FADVISE_SEQUENTIAL); + p->status = OPEN; + p->full_page_printed = false; + ++total_files; + return true; +} + +/* Close the file in P. + + If we aren't dealing with multiple files in parallel, we change + the status of all columns in the column list to reflect the close. */ + +static void +close_file (COLUMN *p) +{ + COLUMN *q; + int i; + + if (p->status == CLOSED) + return; + + int err = errno; + if (!ferror (p->fp)) + err = 0; + if (fileno (p->fp) == STDIN_FILENO) + clearerr (p->fp); + else if (fclose (p->fp) != 0 && !err) + err = errno; + if (err) + die (EXIT_FAILURE, err, "%s", quotef (p->name)); + + if (!parallel_files) + { + for (q = column_vector, i = columns; i; ++q, --i) + { + q->status = CLOSED; + if (q->lines_stored == 0) + { + q->lines_to_print = 0; + } + } + } + else + { + p->status = CLOSED; + p->lines_to_print = 0; + } + + --files_ready_to_read; +} + +/* Put a file on hold until we start a new page, + since we've hit a form feed. + + If we aren't dealing with parallel files, we must change the + status of all columns in the column list. */ + +static void +hold_file (COLUMN *p) +{ + COLUMN *q; + int i; + + if (!parallel_files) + for (q = column_vector, i = columns; i; ++q, --i) + { + if (storing_columns) + q->status = FF_FOUND; + else + q->status = ON_HOLD; + } + else + p->status = ON_HOLD; + + p->lines_to_print = 0; + --files_ready_to_read; +} + +/* Undo hold_file -- go through the column list and change any + ON_HOLD columns to OPEN. Used at the end of each page. */ + +static void +reset_status (void) +{ + int i = columns; + COLUMN *p; + + for (p = column_vector; i; --i, ++p) + if (p->status == ON_HOLD) + { + p->status = OPEN; + files_ready_to_read++; + } + + if (storing_columns) + { + if (column_vector->status == CLOSED) + /* We use the info to output an error message in skip_to_page. */ + files_ready_to_read = 0; + else + files_ready_to_read = 1; + } +} + +/* Print a single file, or multiple files in parallel. + + Set up the list of columns, opening the necessary files. + Allocate space for storing columns, if necessary. + Skip to first_page_number, if user has asked to skip leading pages. + Determine which functions are appropriate to store/print lines + in each column. + Print the file(s). */ + +static void +print_files (int number_of_files, char **av) +{ + init_parameters (number_of_files); + if (! init_fps (number_of_files, av)) + return; + if (storing_columns) + init_store_cols (); + + if (first_page_number > 1) + { + if (!skip_to_page (first_page_number)) + return; + else + page_number = first_page_number; + } + else + page_number = 1; + + init_funcs (); + + line_number = line_count; + while (print_page ()) + ; +} + +/* Initialize header information. + If DESC is non-negative, it is a file descriptor open to + FILENAME for reading. */ + +static void +init_header (char const *filename, int desc) +{ + char *buf = NULL; + struct stat st; + struct timespec t; + int ns; + struct tm tm; + + /* If parallel files or standard input, use current date. */ + if (STREQ (filename, "-")) + desc = -1; + if (0 <= desc && fstat (desc, &st) == 0) + t = get_stat_mtime (&st); + else + { + static struct timespec timespec; + if (! timespec.tv_sec) + gettime (×pec); + t = timespec; + } + + ns = t.tv_nsec; + if (localtime_rz (localtz, &t.tv_sec, &tm)) + { + size_t bufsize + = nstrftime (NULL, SIZE_MAX, date_format, &tm, localtz, ns) + 1; + buf = xmalloc (bufsize); + nstrftime (buf, bufsize, date_format, &tm, localtz, ns); + } + else + { + char secbuf[INT_BUFSIZE_BOUND (intmax_t)]; + buf = xmalloc (sizeof secbuf + MAX (10, INT_BUFSIZE_BOUND (int))); + sprintf (buf, "%s.%09d", timetostr (t.tv_sec, secbuf), ns); + } + + free (date_text); + date_text = buf; + file_text = custom_header ? custom_header : desc < 0 ? "" : filename; + header_width_available = (chars_per_line + - mbswidth (date_text, 0) + - mbswidth (file_text, 0)); +} + +/* Set things up for printing a page + + Scan through the columns ... + Determine which are ready to print + (i.e., which have lines stored or open files) + Set p->lines_to_print appropriately + (to p->lines_stored if we're storing, or lines_per_body + if we're reading straight from the file) + Keep track of this total so we know when to stop printing */ + +static void +init_page (void) +{ + int j; + COLUMN *p; + + if (storing_columns) + { + store_columns (); + for (j = columns - 1, p = column_vector; j; --j, ++p) + { + p->lines_to_print = p->lines_stored; + } + + /* Last column. */ + if (balance_columns) + { + p->lines_to_print = p->lines_stored; + } + /* Since we're not balancing columns, we don't need to store + the rightmost column. Read it straight from the file. */ + else + { + if (p->status == OPEN) + { + p->lines_to_print = lines_per_body; + } + else + p->lines_to_print = 0; + } + } + else + for (j = columns, p = column_vector; j; --j, ++p) + if (p->status == OPEN) + { + p->lines_to_print = lines_per_body; + } + else + p->lines_to_print = 0; +} + +/* Align empty columns and print separators. + Empty columns will be formed by files with status ON_HOLD or CLOSED + when printing multiple files in parallel. */ + +static void +align_column (COLUMN *p) +{ + padding_not_printed = p->start_position; + if (col_sep_length < padding_not_printed) + { + pad_across_to (padding_not_printed - col_sep_length); + padding_not_printed = ANYWHERE; + } + + if (use_col_separator) + print_sep_string (); + + if (p->numbered) + add_line_number (p); +} + +/* Print one page. + + As long as there are lines left on the page and columns ready to print, + Scan across the column list + if the column has stored lines or the file is open + pad to the appropriate spot + print the column + pad the remainder of the page with \n or \f as requested + reset the status of all files -- any files which where on hold because + of formfeeds are now put back into the lineup. */ + +static bool +print_page (void) +{ + int j; + int lines_left_on_page; + COLUMN *p; + + /* Used as an accumulator (with | operator) of successive values of + pad_vertically. The trick is to set pad_vertically + to false before each run through the inner loop, then after that + loop, it tells us whether a line was actually printed (whether a + newline needs to be output -- or two for double spacing). But those + values have to be accumulated (in pv) so we can invoke pad_down + properly after the outer loop completes. */ + bool pv; + + init_page (); + + if (cols_ready_to_print () == 0) + return false; + + if (extremities) + print_a_header = true; + + /* Don't pad unless we know a page was printed. */ + pad_vertically = false; + pv = false; + + lines_left_on_page = lines_per_body; + if (double_space) + lines_left_on_page *= 2; + + while (lines_left_on_page > 0 && cols_ready_to_print () > 0) + { + output_position = 0; + spaces_not_printed = 0; + separators_not_printed = 0; + pad_vertically = false; + align_empty_cols = false; + empty_line = true; + + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + { + input_position = 0; + if (p->lines_to_print > 0 || p->status == FF_FOUND) + { + FF_only = false; + padding_not_printed = p->start_position; + if (!(p->print_func) (p)) + read_rest_of_line (p); + pv |= pad_vertically; + + --p->lines_to_print; + if (p->lines_to_print <= 0) + { + if (cols_ready_to_print () == 0) + break; + } + + /* File p changed its status to ON_HOLD or CLOSED */ + if (parallel_files && p->status != OPEN) + { + if (empty_line) + align_empty_cols = true; + else if (p->status == CLOSED + || (p->status == ON_HOLD && FF_only)) + align_column (p); + } + } + else if (parallel_files) + { + /* File status ON_HOLD or CLOSED */ + if (empty_line) + align_empty_cols = true; + else + align_column (p); + } + + /* We need it also with an empty column */ + if (use_col_separator) + ++separators_not_printed; + } + + if (pad_vertically) + { + putchar ('\n'); + --lines_left_on_page; + } + + if (cols_ready_to_print () == 0 && !extremities) + break; + + if (double_space && pv) + { + putchar ('\n'); + --lines_left_on_page; + } + } + + if (lines_left_on_page == 0) + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + p->full_page_printed = true; + + pad_vertically = pv; + + if (pad_vertically && extremities) + pad_down (lines_left_on_page + lines_per_footer); + else if (keep_FF && print_a_FF) + { + putchar ('\f'); + print_a_FF = false; + } + + if (last_page_number < ++page_number) + return false; /* Stop printing with LAST_PAGE */ + + reset_status (); /* Change ON_HOLD to OPEN. */ + + return true; /* More pages to go. */ +} + +/* Allocate space for storing columns. + + This is necessary when printing multiple columns from a single file. + Lines are stored consecutively in buff, separated by '\0'. + + The following doesn't apply any longer - any tuning possible? + (We can't use a fixed offset since with the '-s' flag lines aren't + truncated.) + + We maintain a list (line_vector) of pointers to the beginnings + of lines in buff. We allocate one more than the number of lines + because the last entry tells us the index of the last character, + which we need to know in order to print the last line in buff. */ + +static void +init_store_cols (void) +{ + int total_lines, total_lines_1, chars_per_column_1, chars_if_truncate; + if (INT_MULTIPLY_WRAPV (lines_per_body, columns, &total_lines) + || INT_ADD_WRAPV (total_lines, 1, &total_lines_1) + || INT_ADD_WRAPV (chars_per_column, 1, &chars_per_column_1) + || INT_MULTIPLY_WRAPV (total_lines, chars_per_column_1, + &chars_if_truncate)) + integer_overflow (); + + free (line_vector); + /* FIXME: here's where it was allocated. */ + line_vector = xnmalloc (total_lines_1, sizeof *line_vector); + + free (end_vector); + end_vector = xnmalloc (total_lines, sizeof *end_vector); + + free (buff); + buff = xnmalloc (chars_if_truncate, use_col_separator + 1); + buff_allocated = chars_if_truncate; /* Tune this. */ + buff_allocated *= use_col_separator + 1; +} + +/* Store all but the rightmost column. + (Used when printing a single file in multiple downward columns) + + For each column + set p->current_line to be the index in line_vector of the + first line in the column + For each line in the column + store the line in buff + add to line_vector the index of the line's first char + buff_start is the index in buff of the first character in the + current line. */ + +static void +store_columns (void) +{ + int i, j; + unsigned int line = 0; + unsigned int buff_start; + int last_col; /* The rightmost column which will be saved in buff */ + COLUMN *p; + + buff_current = 0; + buff_start = 0; + + if (balance_columns) + last_col = columns; + else + last_col = columns - 1; + + for (i = 1, p = column_vector; i <= last_col; ++i, ++p) + p->lines_stored = 0; + + for (i = 1, p = column_vector; i <= last_col && files_ready_to_read; + ++i, ++p) + { + p->current_line = line; + for (j = lines_per_body; j && files_ready_to_read; --j) + + if (p->status == OPEN) /* Redundant. Clean up. */ + { + input_position = 0; + + if (!read_line (p)) + read_rest_of_line (p); + + if (p->status == OPEN + || buff_start != buff_current) + { + ++p->lines_stored; + line_vector[line] = buff_start; + end_vector[line++] = input_position; + buff_start = buff_current; + } + } + } + + /* Keep track of the location of the last char in buff. */ + line_vector[line] = buff_start; + + if (balance_columns) + balance (line); +} + +static void +balance (int total_stored) +{ + COLUMN *p; + int i, lines; + int first_line = 0; + + for (i = 1, p = column_vector; i <= columns; ++i, ++p) + { + lines = total_stored / columns; + if (i <= total_stored % columns) + ++lines; + + p->lines_stored = lines; + p->current_line = first_line; + + first_line += lines; + } +} + +/* Store a character in the buffer. */ + +static void +store_char (char c) +{ + if (buff_current >= buff_allocated) + { + /* May be too generous. */ + buff = X2REALLOC (buff, &buff_allocated); + } + buff[buff_current++] = c; +} + +static void +add_line_number (COLUMN *p) +{ + int i; + char *s; + int num_width; + + /* Cutting off the higher-order digits is more informative than + lower-order cut off. */ + num_width = sprintf (number_buff, "%*d", chars_per_number, line_number); + line_number++; + s = number_buff + (num_width - chars_per_number); + for (i = chars_per_number; i > 0; i--) + (p->char_func) (*s++); + + if (columns > 1) + { + /* Tabification is assumed for multiple columns, also for n-separators, + but 'default n-separator = TAB' hasn't been given priority over + equal column_width also specified by POSIX. */ + if (number_separator == '\t') + { + i = number_width - chars_per_number; + while (i-- > 0) + (p->char_func) (' '); + } + else + (p->char_func) (number_separator); + } + else + /* To comply with POSIX, we avoid any expansion of default TAB + separator with a single column output. No column_width requirement + has to be considered. */ + { + (p->char_func) (number_separator); + if (number_separator == '\t') + output_position = POS_AFTER_TAB (chars_per_output_tab, + output_position); + } + + if (truncate_lines && !parallel_files) + input_position += number_width; +} + +/* Print (or store) padding until the current horizontal position + is position. */ + +static void +pad_across_to (int position) +{ + int h = output_position; + + if (tabify_output) + spaces_not_printed = position - output_position; + else + { + while (++h <= position) + putchar (' '); + output_position = position; + } +} + +/* Pad to the bottom of the page. + + If the user has requested a formfeed, use one. + Otherwise, use newlines. */ + +static void +pad_down (unsigned int lines) +{ + if (use_form_feed) + putchar ('\f'); + else + for (unsigned int i = lines; i; --i) + putchar ('\n'); +} + +/* Read the rest of the line. + + Read from the current column's file until an end of line is + hit. Used when we've truncated a line and we no longer need + to print or store its characters. */ + +static void +read_rest_of_line (COLUMN *p) +{ + int c; + FILE *f = p->fp; + + while ((c = getc (f)) != '\n') + { + if (c == '\f') + { + if ((c = getc (f)) != '\n') + ungetc (c, f); + if (keep_FF) + print_a_FF = true; + hold_file (p); + break; + } + else if (c == EOF) + { + close_file (p); + break; + } + } +} + +/* Read a line with skip_to_page. + + Read from the current column's file until an end of line is + hit. Used when we read full lines to skip pages. + With skip_to_page we have to check for FF-coincidence which is done + in function read_line otherwise. + Count lines of skipped pages to find the line number of 1st page + printed relative to 1st line of input file (start_line_num). */ + +static void +skip_read (COLUMN *p, int column_number) +{ + int c; + FILE *f = p->fp; + int i; + bool single_ff = false; + COLUMN *q; + + /* Read 1st character in a line or any character succeeding a FF */ + if ((c = getc (f)) == '\f' && p->full_page_printed) + /* A FF-coincidence with a previous full_page_printed. + To avoid an additional empty page, eliminate the FF */ + if ((c = getc (f)) == '\n') + c = getc (f); + + p->full_page_printed = false; + + /* 1st character a FF means a single FF without any printable + characters. Don't count it as a line with -n option. */ + if (c == '\f') + single_ff = true; + + /* Preparing for a FF-coincidence: Maybe we finish that page + without a FF found */ + if (last_line) + p->full_page_printed = true; + + while (c != '\n') + { + if (c == '\f') + { + /* No FF-coincidence possible, + no catching up of a FF-coincidence with next page */ + if (last_line) + { + if (!parallel_files) + for (q = column_vector, i = columns; i; ++q, --i) + q->full_page_printed = false; + else + p->full_page_printed = false; + } + + if ((c = getc (f)) != '\n') + ungetc (c, f); + hold_file (p); + break; + } + else if (c == EOF) + { + close_file (p); + break; + } + c = getc (f); + } + + if (skip_count) + if ((!parallel_files || column_number == 1) && !single_ff) + ++line_count; +} + +/* If we're tabifying output, + + When print_char encounters white space it keeps track + of our desired horizontal position and delays printing + until this function is called. */ + +static void +print_white_space (void) +{ + int h_new; + int h_old = output_position; + int goal = h_old + spaces_not_printed; + + while (goal - h_old > 1 + && (h_new = POS_AFTER_TAB (chars_per_output_tab, h_old)) <= goal) + { + putchar (output_tab_char); + h_old = h_new; + } + while (++h_old <= goal) + putchar (' '); + + output_position = goal; + spaces_not_printed = 0; +} + +/* Print column separators. + + We keep a count until we know that we'll be printing a line, + then print_sep_string() is called. */ + +static void +print_sep_string (void) +{ + char const *s = col_sep_string; + int l = col_sep_length; + + if (separators_not_printed <= 0) + { + /* We'll be starting a line with chars_per_margin, anything else? */ + if (spaces_not_printed > 0) + print_white_space (); + } + else + { + for (; separators_not_printed > 0; --separators_not_printed) + { + while (l-- > 0) + { + /* 3 types of sep_strings: spaces only, spaces and chars, + chars only */ + if (*s == ' ') + { + /* We're tabifying output; consecutive spaces in + sep_string may have to be converted to tabs */ + s++; + ++spaces_not_printed; + } + else + { + if (spaces_not_printed > 0) + print_white_space (); + putchar (*s++); + ++output_position; + } + } + /* sep_string ends with some spaces */ + if (spaces_not_printed > 0) + print_white_space (); + } + } +} + +/* Print (or store, depending on p->char_func) a clump of N + characters. */ + +static void +print_clump (COLUMN *p, int n, char *clump) +{ + while (n--) + (p->char_func) (*clump++); +} + +/* Print a character. + + Update the following comment: process-char hasn't been used any + longer. + If we're tabifying, all tabs have been converted to spaces by + process_char(). Keep a count of consecutive spaces, and when + a nonspace is encountered, call print_white_space() to print the + required number of tabs and spaces. */ + +static void +print_char (char c) +{ + if (tabify_output) + { + if (c == ' ') + { + ++spaces_not_printed; + return; + } + else if (spaces_not_printed > 0) + print_white_space (); + + /* Nonprintables are assumed to have width 0, except '\b'. */ + if (! isprint (to_uchar (c))) + { + if (c == '\b') + --output_position; + } + else + ++output_position; + } + putchar (c); +} + +/* Skip to page PAGE before printing. + PAGE may be larger than total number of pages. */ + +static bool +skip_to_page (uintmax_t page) +{ + for (uintmax_t n = 1; n < page; ++n) + { + COLUMN *p; + int j; + + for (int i = 1; i < lines_per_body; ++i) + { + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + skip_read (p, j); + } + last_line = true; + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + skip_read (p, j); + + if (storing_columns) /* change FF_FOUND to ON_HOLD */ + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status != CLOSED) + p->status = ON_HOLD; + + reset_status (); + last_line = false; + + if (files_ready_to_read < 1) + { + /* It's very helpful, normally the total number of pages is + not known in advance. */ + error (0, 0, + _("starting page number %"PRIuMAX + " exceeds page count %"PRIuMAX), + page, n); + break; + } + } + return files_ready_to_read > 0; +} + +/* Print a header. + + Formfeeds are assumed to use up two lines at the beginning of + the page. */ + +static void +print_header (void) +{ + char page_text[256 + INT_STRLEN_BOUND (page_number)]; + int available_width; + int lhs_spaces; + int rhs_spaces; + + output_position = 0; + pad_across_to (chars_per_margin); + print_white_space (); + + if (page_number == 0) + die (EXIT_FAILURE, 0, _("page number overflow")); + + /* The translator must ensure that formatting the translation of + "Page %"PRIuMAX does not generate more than (sizeof page_text - 1) + bytes. */ + sprintf (page_text, _("Page %"PRIuMAX), page_number); + available_width = header_width_available - mbswidth (page_text, 0); + available_width = MAX (0, available_width); + lhs_spaces = available_width >> 1; + rhs_spaces = available_width - lhs_spaces; + + printf ("\n\n%*s%s%*s%s%*s%s\n\n\n", + chars_per_margin, "", + date_text, lhs_spaces, " ", + file_text, rhs_spaces, " ", page_text); + + print_a_header = false; + output_position = 0; +} + +/* Print (or store, if p->char_func is store_char()) a line. + + Read a character to determine whether we have a line or not. + (We may hit EOF, \n, or \f) + + Once we know we have a line, + set pad_vertically = true, meaning it's safe + to pad down at the end of the page, since we do have a page. + print a header if needed. + pad across to padding_not_printed if needed. + print any separators which need to be printed. + print a line number if it needs to be printed. + + Print the clump which corresponds to the first character. + + Enter a loop and keep printing until an end of line condition + exists, or until we exceed chars_per_column. + + Return false if we exceed chars_per_column before reading + an end of line character, true otherwise. */ + +static bool +read_line (COLUMN *p) +{ + int c; + int chars; + int last_input_position; + int j, k; + COLUMN *q; + + /* read 1st character in each line or any character succeeding a FF: */ + c = getc (p->fp); + + last_input_position = input_position; + + if (c == '\f' && p->full_page_printed) + if ((c = getc (p->fp)) == '\n') + c = getc (p->fp); + p->full_page_printed = false; + + switch (c) + { + case '\f': + if ((c = getc (p->fp)) != '\n') + ungetc (c, p->fp); + FF_only = true; + if (print_a_header && !storing_columns) + { + pad_vertically = true; + print_header (); + } + else if (keep_FF) + print_a_FF = true; + hold_file (p); + return true; + case EOF: + close_file (p); + return true; + case '\n': + break; + default: + chars = char_to_clump (c); + } + + if (truncate_lines && input_position > chars_per_column) + { + input_position = last_input_position; + return false; + } + + if (p->char_func != store_char) + { + pad_vertically = true; + + if (print_a_header && !storing_columns) + print_header (); + + if (parallel_files && align_empty_cols) + { + /* We have to align empty columns at the beginning of a line. */ + k = separators_not_printed; + separators_not_printed = 0; + for (j = 1, q = column_vector; j <= k; ++j, ++q) + { + align_column (q); + separators_not_printed += 1; + } + padding_not_printed = p->start_position; + if (truncate_lines) + spaces_not_printed = chars_per_column; + else + spaces_not_printed = 0; + align_empty_cols = false; + } + + if (col_sep_length < padding_not_printed) + { + pad_across_to (padding_not_printed - col_sep_length); + padding_not_printed = ANYWHERE; + } + + if (use_col_separator) + print_sep_string (); + } + + if (p->numbered) + add_line_number (p); + + empty_line = false; + if (c == '\n') + return true; + + print_clump (p, chars, clump_buff); + + while (true) + { + c = getc (p->fp); + + switch (c) + { + case '\n': + return true; + case '\f': + if ((c = getc (p->fp)) != '\n') + ungetc (c, p->fp); + if (keep_FF) + print_a_FF = true; + hold_file (p); + return true; + case EOF: + close_file (p); + return true; + } + + last_input_position = input_position; + chars = char_to_clump (c); + if (truncate_lines && input_position > chars_per_column) + { + input_position = last_input_position; + return false; + } + + print_clump (p, chars, clump_buff); + } +} + +/* Print a line from buff. + + If this function has been called, we know we have "something to + print". But it remains to be seen whether we have a real text page + or an empty page (a single form feed) with/without a header only. + Therefore first we set pad_vertically to true and print a header + if necessary. + If FF_FOUND and we are using -t|-T option we omit any newline by + setting pad_vertically to false (see print_page). + Otherwise we pad across if necessary, print separators if necessary + and text of COLUMN *p. + + Return true, meaning there is no need to call read_rest_of_line. */ + +static bool +print_stored (COLUMN *p) +{ + COLUMN *q; + + int line = p->current_line++; + char *first = &buff[line_vector[line]]; + /* FIXME + UMR: Uninitialized memory read: + * This is occurring while in: + print_stored [pr.c:2239] + * Reading 4 bytes from 0x5148c in the heap. + * Address 0x5148c is 4 bytes into a malloc'd block at 0x51488 of 676 bytes + * This block was allocated from: + malloc [rtlib.o] + xmalloc [xmalloc.c:94] + init_store_cols [pr.c:1648] + */ + char *last = &buff[line_vector[line + 1]]; + + pad_vertically = true; + + if (print_a_header) + print_header (); + + if (p->status == FF_FOUND) + { + int i; + for (i = 1, q = column_vector; i <= columns; ++i, ++q) + q->status = ON_HOLD; + if (column_vector->lines_to_print <= 0) + { + if (!extremities) + pad_vertically = false; + return true; /* print a header only */ + } + } + + if (col_sep_length < padding_not_printed) + { + pad_across_to (padding_not_printed - col_sep_length); + padding_not_printed = ANYWHERE; + } + + if (use_col_separator) + print_sep_string (); + + while (first != last) + print_char (*first++); + + if (spaces_not_printed == 0) + { + output_position = p->start_position + end_vector[line]; + if (p->start_position - col_sep_length == chars_per_margin) + output_position -= col_sep_length; + } + + return true; +} + +/* Convert a character to the proper format and return the number of + characters in the resulting clump. Increment input_position by + the width of the clump. + + Tabs are converted to clumps of spaces. + Nonprintable characters may be converted to clumps of escape + sequences or control prefixes. + + Note: the width of a clump is not necessarily equal to the number of + characters in clump_buff. (e.g, the width of '\b' is -1, while the + number of characters is 1.) */ + +static int +char_to_clump (char c) +{ + unsigned char uc = c; + char *s = clump_buff; + int i; + char esc_buff[4]; + int width; + int chars; + int chars_per_c = 8; + + if (c == input_tab_char) + chars_per_c = chars_per_input_tab; + + if (c == input_tab_char || c == '\t') + { + width = TAB_WIDTH (chars_per_c, input_position); + + if (untabify_input) + { + for (i = width; i; --i) + *s++ = ' '; + chars = width; + } + else + { + *s = c; + chars = 1; + } + + } + else if (! isprint (uc)) + { + if (use_esc_sequence) + { + width = 4; + chars = 4; + *s++ = '\\'; + sprintf (esc_buff, "%03o", uc); + for (i = 0; i <= 2; ++i) + *s++ = esc_buff[i]; + } + else if (use_cntrl_prefix) + { + if (uc < 0200) + { + width = 2; + chars = 2; + *s++ = '^'; + *s = c ^ 0100; + } + else + { + width = 4; + chars = 4; + *s++ = '\\'; + sprintf (esc_buff, "%03o", uc); + for (i = 0; i <= 2; ++i) + *s++ = esc_buff[i]; + } + } + else if (c == '\b') + { + width = -1; + chars = 1; + *s = c; + } + else + { + width = 0; + chars = 1; + *s = c; + } + } + else + { + width = 1; + chars = 1; + *s = c; + } + + /* Too many backspaces must put us in position 0 -- never negative. */ + if (width < 0 && input_position == 0) + { + chars = 0; + input_position = 0; + } + else if (width < 0 && input_position <= -width) + input_position = 0; + else + input_position += width; + + return chars; +} + +/* We've just printed some files and need to clean up things before + looking for more options and printing the next batch of files. + + Free everything we've xmalloc'ed, except 'header'. */ + +static void +cleanup (void) +{ + free (number_buff); + free (clump_buff); + free (column_vector); + free (line_vector); + free (end_vector); + free (buff); +} + +/* Complain, print a usage message, and die. */ + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + emit_try_help (); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + + fputs (_("\ +Paginate or columnate FILE(s) for printing.\n\ +"), stdout); + + emit_stdin_note (); + emit_mandatory_arg_note (); + + fputs (_("\ + +FIRST_PAGE[:LAST_PAGE], --pages=FIRST_PAGE[:LAST_PAGE]\n\ + begin [stop] printing with page FIRST_[LAST_]PAGE\n\ + -COLUMN, --columns=COLUMN\n\ + output COLUMN columns and print columns down,\n\ + unless -a is used. Balance number of lines in the\n\ + columns on each page\n\ +"), stdout); + fputs (_("\ + -a, --across print columns across rather than down, used together\n\ + with -COLUMN\n\ + -c, --show-control-chars\n\ + use hat notation (^G) and octal backslash notation\n\ + -d, --double-space\n\ + double space the output\n\ +"), stdout); + fputs (_("\ + -D, --date-format=FORMAT\n\ + use FORMAT for the header date\n\ + -e[CHAR[WIDTH]], --expand-tabs[=CHAR[WIDTH]]\n\ + expand input CHARs (TABs) to tab WIDTH (8)\n\ + -F, -f, --form-feed\n\ + use form feeds instead of newlines to separate pages\n\ + (by a 3-line page header with -F or a 5-line header\n\ + and trailer without -F)\n\ +"), stdout); + fputs (_("\ + -h, --header=HEADER\n\ + use a centered HEADER instead of filename in page header,\n\ + -h \"\" prints a blank line, don't use -h\"\"\n\ + -i[CHAR[WIDTH]], --output-tabs[=CHAR[WIDTH]]\n\ + replace spaces with CHARs (TABs) to tab WIDTH (8)\n\ + -J, --join-lines merge full lines, turns off -W line truncation, no column\n\ + alignment, --sep-string[=STRING] sets separators\n\ +"), stdout); + fputs (_("\ + -l, --length=PAGE_LENGTH\n\ + set the page length to PAGE_LENGTH (66) lines\n\ + (default number of lines of text 56, and with -F 63).\n\ + implies -t if PAGE_LENGTH <= 10\n\ +"), stdout); + fputs (_("\ + -m, --merge print all files in parallel, one in each column,\n\ + truncate lines, but join lines of full length with -J\n\ +"), stdout); + fputs (_("\ + -n[SEP[DIGITS]], --number-lines[=SEP[DIGITS]]\n\ + number lines, use DIGITS (5) digits, then SEP (TAB),\n\ + default counting starts with 1st line of input file\n\ + -N, --first-line-number=NUMBER\n\ + start counting with NUMBER at 1st line of first\n\ + page printed (see +FIRST_PAGE)\n\ +"), stdout); + fputs (_("\ + -o, --indent=MARGIN\n\ + offset each line with MARGIN (zero) spaces, do not\n\ + affect -w or -W, MARGIN will be added to PAGE_WIDTH\n\ + -r, --no-file-warnings\n\ + omit warning when a file cannot be opened\n\ +"), stdout); + fputs (_("\ + -s[CHAR], --separator[=CHAR]\n\ + separate columns by a single character, default for CHAR\n\ + is the <TAB> character without -w and \'no char\' with -w.\ +\n\ + -s[CHAR] turns off line truncation of all 3 column\n\ + options (-COLUMN|-a -COLUMN|-m) except -w is set\n\ +"), stdout); + fputs (_("\ + -S[STRING], --sep-string[=STRING]\n\ + separate columns by STRING,\n\ + without -S: Default separator <TAB> with -J and <space>\n\ + otherwise (same as -S\" \"), no effect on column options\n\ +"), stdout); + fputs (_("\ + -t, --omit-header omit page headers and trailers;\n\ + implied if PAGE_LENGTH <= 10\n\ +"), stdout); + fputs (_("\ + -T, --omit-pagination\n\ + omit page headers and trailers, eliminate any pagination\n\ + by form feeds set in input files\n\ + -v, --show-nonprinting\n\ + use octal backslash notation\n\ + -w, --width=PAGE_WIDTH\n\ + set page width to PAGE_WIDTH (72) characters for\n\ + multiple text-column output only, -s[char] turns off (72)\n\ +"), stdout); + fputs (_("\ + -W, --page-width=PAGE_WIDTH\n\ + set page width to PAGE_WIDTH (72) characters always,\n\ + truncate lines, except -J option is set, no interference\n\ + with -S or -s\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + emit_ancillary_info (PROGRAM_NAME); + } + exit (status); +} |