| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| #include <config.h> |
|
|
| #include <stdio.h> |
| #include <getopt.h> |
| #include <sys/types.h> |
|
|
| #if HAVE_STROPTS_H |
| # include <stropts.h> |
| #endif |
| #include <sys/ioctl.h> |
|
|
| #include "system.h" |
| #include "alignalloc.h" |
| #include "ioblksize.h" |
| #include "fadvise.h" |
| #include "full-write.h" |
| #include "xbinary-io.h" |
|
|
| |
| #define PROGRAM_NAME "cat" |
|
|
| #define AUTHORS \ |
| proper_name_lite ("Torbjorn Granlund", "Torbj\303\266rn Granlund"), \ |
| proper_name ("Richard M. Stallman") |
|
|
| |
| static char const *infile; |
|
|
| |
| static int input_desc; |
|
|
| |
| |
| |
| #define LINE_COUNTER_BUF_LEN 20 |
| static char line_buf[LINE_COUNTER_BUF_LEN] = |
| { |
| ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', |
| ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '0', |
| '\t', '\0' |
| }; |
|
|
| |
| |
| static char *line_num_print = line_buf + LINE_COUNTER_BUF_LEN - 8; |
|
|
| |
| static char *line_num_start = line_buf + LINE_COUNTER_BUF_LEN - 3; |
|
|
| |
| static char *line_num_end = line_buf + LINE_COUNTER_BUF_LEN - 3; |
|
|
| |
| static int newlines2 = 0; |
|
|
| |
| static bool pending_cr = false; |
|
|
| void |
| usage (int status) |
| { |
| if (status != EXIT_SUCCESS) |
| emit_try_help (); |
| else |
| { |
| printf (_("\ |
| Usage: %s [OPTION]... [FILE]...\n\ |
| "), |
| program_name); |
| fputs (_("\ |
| Concatenate FILE(s) to standard output.\n\ |
| "), stdout); |
|
|
| emit_stdin_note (); |
|
|
| fputs (_("\ |
| \n\ |
| -A, --show-all equivalent to -vET\n\ |
| -b, --number-nonblank number nonempty output lines, overrides -n\n\ |
| -e equivalent to -vE\n\ |
| -E, --show-ends display $ at end of each line\n\ |
| -n, --number number all output lines\n\ |
| -s, --squeeze-blank suppress repeated empty output lines\n\ |
| "), stdout); |
| fputs (_("\ |
| -t equivalent to -vT\n\ |
| -T, --show-tabs display TAB characters as ^I\n\ |
| -u (ignored)\n\ |
| -v, --show-nonprinting use ^ and M- notation, except for LFD and TAB\n\ |
| "), stdout); |
| fputs (HELP_OPTION_DESCRIPTION, stdout); |
| fputs (VERSION_OPTION_DESCRIPTION, stdout); |
| printf (_("\ |
| \n\ |
| Examples:\n\ |
| %s f - g Output f's contents, then standard input, then g's contents.\n\ |
| %s Copy standard input to standard output.\n\ |
| "), |
| program_name, program_name); |
| emit_ancillary_info (PROGRAM_NAME); |
| } |
| exit (status); |
| } |
|
|
| |
|
|
| static void |
| next_line_num (void) |
| { |
| char *endp = line_num_end; |
| do |
| { |
| if ((*endp)++ < '9') |
| return; |
| *endp-- = '0'; |
| } |
| while (endp >= line_num_start); |
|
|
| if (line_num_start > line_buf) |
| *--line_num_start = '1'; |
| else |
| *line_buf = '>'; |
| if (line_num_start < line_num_print) |
| line_num_print--; |
| } |
|
|
| |
| |
| |
|
|
| static bool |
| simple_cat (char *buf, idx_t bufsize) |
| { |
| |
|
|
| while (true) |
| { |
| |
|
|
| ssize_t n_read = read (input_desc, buf, bufsize); |
| if (n_read < 0) |
| { |
| error (0, errno, "%s", quotef (infile)); |
| return false; |
| } |
|
|
| |
|
|
| if (n_read == 0) |
| return true; |
|
|
| |
|
|
| if (full_write (STDOUT_FILENO, buf, n_read) != n_read) |
| write_error (); |
| } |
| } |
|
|
| |
| |
| |
|
|
| static inline void |
| write_pending (char *outbuf, char **bpout) |
| { |
| idx_t n_write = *bpout - outbuf; |
| if (0 < n_write) |
| { |
| if (full_write (STDOUT_FILENO, outbuf, n_write) != n_write) |
| write_error (); |
| *bpout = outbuf; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| static bool |
| cat (char *inbuf, idx_t insize, char *outbuf, idx_t outsize, |
| bool show_nonprinting, bool show_tabs, bool number, bool number_nonblank, |
| bool show_ends, bool squeeze_blank) |
| { |
| |
| unsigned char ch; |
|
|
| |
| |
| |
| |
| |
| int newlines = newlines2; |
|
|
| #ifdef FIONREAD |
| |
| |
| bool use_fionread = true; |
| #endif |
|
|
| |
| |
|
|
| |
| |
| char *eob = inbuf; |
|
|
| |
| char *bpin = eob + 1; |
|
|
| |
| char *bpout = outbuf; |
|
|
| while (true) |
| { |
| do |
| { |
| |
|
|
| if (outbuf + outsize <= bpout) |
| { |
| char *wp = outbuf; |
| idx_t remaining_bytes; |
| do |
| { |
| if (full_write (STDOUT_FILENO, wp, outsize) != outsize) |
| write_error (); |
| wp += outsize; |
| remaining_bytes = bpout - wp; |
| } |
| while (outsize <= remaining_bytes); |
|
|
| |
| |
|
|
| memmove (outbuf, wp, remaining_bytes); |
| bpout = outbuf + remaining_bytes; |
| } |
|
|
| |
|
|
| if (bpin > eob) |
| { |
| bool input_pending = false; |
| #ifdef FIONREAD |
| int n_to_read = 0; |
|
|
| |
| |
| |
|
|
| if (use_fionread |
| && ioctl (input_desc, FIONREAD, &n_to_read) < 0) |
| { |
| |
| |
| |
| |
| |
| |
| if (errno == EOPNOTSUPP || errno == ENOTTY |
| || errno == EINVAL || errno == ENODEV |
| || errno == ENOSYS) |
| use_fionread = false; |
| else |
| { |
| error (0, errno, _("cannot do ioctl on %s"), |
| quoteaf (infile)); |
| newlines2 = newlines; |
| return false; |
| } |
| } |
| if (n_to_read != 0) |
| input_pending = true; |
| #endif |
|
|
| if (!input_pending) |
| write_pending (outbuf, &bpout); |
|
|
| |
|
|
| ssize_t n_read = read (input_desc, inbuf, insize); |
| if (n_read < 0) |
| { |
| error (0, errno, "%s", quotef (infile)); |
| write_pending (outbuf, &bpout); |
| newlines2 = newlines; |
| return false; |
| } |
| if (n_read == 0) |
| { |
| write_pending (outbuf, &bpout); |
| newlines2 = newlines; |
| return true; |
| } |
|
|
| |
| |
|
|
| bpin = inbuf; |
| eob = bpin + n_read; |
| *eob = '\n'; |
| } |
| else |
| { |
| |
|
|
| |
| |
|
|
| if (++newlines > 0) |
| { |
| if (newlines >= 2) |
| { |
| |
| |
| |
| newlines = 2; |
|
|
| |
| |
| |
| if (squeeze_blank) |
| { |
| ch = *bpin++; |
| continue; |
| } |
| } |
|
|
| |
|
|
| if (number && !number_nonblank) |
| { |
| next_line_num (); |
| bpout = stpcpy (bpout, line_num_print); |
| } |
| } |
|
|
| |
| if (show_ends) |
| { |
| if (pending_cr) |
| { |
| *bpout++ = '^'; |
| *bpout++ = 'M'; |
| pending_cr = false; |
| } |
| *bpout++ = '$'; |
| } |
|
|
| |
|
|
| *bpout++ = '\n'; |
| } |
| ch = *bpin++; |
| } |
| while (ch == '\n'); |
|
|
| |
|
|
| if (pending_cr) |
| { |
| *bpout++ = '\r'; |
| pending_cr = false; |
| } |
|
|
| |
|
|
| if (newlines >= 0 && number) |
| { |
| next_line_num (); |
| bpout = stpcpy (bpout, line_num_print); |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| if (show_nonprinting) |
| { |
| while (true) |
| { |
| if (ch >= 32) |
| { |
| if (ch < 127) |
| *bpout++ = ch; |
| else if (ch == 127) |
| { |
| *bpout++ = '^'; |
| *bpout++ = '?'; |
| } |
| else |
| { |
| *bpout++ = 'M'; |
| *bpout++ = '-'; |
| if (ch >= 128 + 32) |
| { |
| if (ch < 128 + 127) |
| *bpout++ = ch - 128; |
| else |
| { |
| *bpout++ = '^'; |
| *bpout++ = '?'; |
| } |
| } |
| else |
| { |
| *bpout++ = '^'; |
| *bpout++ = ch - 128 + 64; |
| } |
| } |
| } |
| else if (ch == '\t' && !show_tabs) |
| *bpout++ = '\t'; |
| else if (ch == '\n') |
| { |
| newlines = -1; |
| break; |
| } |
| else |
| { |
| *bpout++ = '^'; |
| *bpout++ = ch + 64; |
| } |
|
|
| ch = *bpin++; |
| } |
| } |
| else |
| { |
| |
| while (true) |
| { |
| if (ch == '\t' && show_tabs) |
| { |
| *bpout++ = '^'; |
| *bpout++ = ch + 64; |
| } |
| else if (ch != '\n') |
| { |
| if (ch == '\r' && *bpin == '\n' && show_ends) |
| { |
| if (bpin == eob) |
| pending_cr = true; |
| else |
| { |
| *bpout++ = '^'; |
| *bpout++ = 'M'; |
| } |
| } |
| else |
| *bpout++ = ch; |
| } |
| else |
| { |
| newlines = -1; |
| break; |
| } |
|
|
| ch = *bpin++; |
| } |
| } |
| } |
| } |
|
|
| |
| |
| |
|
|
| static int |
| copy_cat (void) |
| { |
| |
| |
| |
| ssize_t copy_max = MIN (SSIZE_MAX, SIZE_MAX) >> 30 << 30; |
|
|
| |
| |
| |
| |
| |
|
|
| for (bool some_copied = false; ; some_copied = true) |
| switch (copy_file_range (input_desc, nullptr, STDOUT_FILENO, nullptr, |
| copy_max, 0)) |
| { |
| case 0: |
| return some_copied; |
|
|
| case -1: |
| if (errno == ENOSYS || is_ENOTSUP (errno) || errno == EINVAL |
| || errno == EBADF || errno == EXDEV || errno == ETXTBSY |
| || errno == EPERM) |
| return 0; |
| error (0, errno, "%s", quotef (infile)); |
| return -1; |
| } |
| } |
|
|
|
|
| int |
| main (int argc, char **argv) |
| { |
| |
| bool have_read_stdin = false; |
|
|
| struct stat stat_buf; |
|
|
| |
| bool number = false; |
| bool number_nonblank = false; |
| bool squeeze_blank = false; |
| bool show_ends = false; |
| bool show_nonprinting = false; |
| bool show_tabs = false; |
| int file_open_mode = O_RDONLY; |
|
|
| static struct option const long_options[] = |
| { |
| {"number-nonblank", no_argument, nullptr, 'b'}, |
| {"number", no_argument, nullptr, 'n'}, |
| {"squeeze-blank", no_argument, nullptr, 's'}, |
| {"show-nonprinting", no_argument, nullptr, 'v'}, |
| {"show-ends", no_argument, nullptr, 'E'}, |
| {"show-tabs", no_argument, nullptr, 'T'}, |
| {"show-all", no_argument, nullptr, 'A'}, |
| {GETOPT_HELP_OPTION_DECL}, |
| {GETOPT_VERSION_OPTION_DECL}, |
| {nullptr, 0, nullptr, 0} |
| }; |
|
|
| initialize_main (&argc, &argv); |
| set_program_name (argv[0]); |
| setlocale (LC_ALL, ""); |
| bindtextdomain (PACKAGE, LOCALEDIR); |
| textdomain (PACKAGE); |
|
|
| |
| |
| |
| |
| atexit (close_stdout); |
|
|
| |
|
|
| int c; |
| while ((c = getopt_long (argc, argv, "benstuvAET", long_options, nullptr)) |
| != -1) |
| { |
| switch (c) |
| { |
| case 'b': |
| number = true; |
| number_nonblank = true; |
| break; |
|
|
| case 'e': |
| show_ends = true; |
| show_nonprinting = true; |
| break; |
|
|
| case 'n': |
| number = true; |
| break; |
|
|
| case 's': |
| squeeze_blank = true; |
| break; |
|
|
| case 't': |
| show_tabs = true; |
| show_nonprinting = true; |
| break; |
|
|
| case 'u': |
| |
| break; |
|
|
| case 'v': |
| show_nonprinting = true; |
| break; |
|
|
| case 'A': |
| show_nonprinting = true; |
| show_ends = true; |
| show_tabs = true; |
| break; |
|
|
| case 'E': |
| show_ends = true; |
| break; |
|
|
| case 'T': |
| show_tabs = true; |
| break; |
|
|
| case_GETOPT_HELP_CHAR; |
|
|
| case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); |
|
|
| default: |
| usage (EXIT_FAILURE); |
| } |
| } |
|
|
| |
|
|
| if (fstat (STDOUT_FILENO, &stat_buf) < 0) |
| error (EXIT_FAILURE, errno, _("standard output")); |
|
|
| |
| idx_t outsize = io_blksize (&stat_buf); |
|
|
| |
| struct |
| { |
| dev_t st_dev; |
| ino_t st_ino; |
| } out_id; |
| int out_flags = -2; |
| bool have_out_dev = ! (S_TYPEISSHM (&stat_buf) || S_TYPEISTMO (&stat_buf)); |
| if (have_out_dev) |
| { |
| out_id.st_dev = stat_buf.st_dev; |
| out_id.st_ino = stat_buf.st_ino; |
| } |
|
|
| |
| bool out_isreg = S_ISREG (stat_buf.st_mode) != 0; |
|
|
| if (! (number || show_ends || squeeze_blank)) |
| { |
| file_open_mode |= O_BINARY; |
| xset_binary_mode (STDOUT_FILENO, O_BINARY); |
| } |
|
|
| |
|
|
| infile = "-"; |
| int argind = optind; |
| bool ok = true; |
| idx_t page_size = getpagesize (); |
|
|
| do |
| { |
| if (argind < argc) |
| infile = argv[argind]; |
|
|
| bool reading_stdin = streq (infile, "-"); |
| if (reading_stdin) |
| { |
| have_read_stdin = true; |
| input_desc = STDIN_FILENO; |
| if (file_open_mode & O_BINARY) |
| xset_binary_mode (STDIN_FILENO, O_BINARY); |
| } |
| else |
| { |
| input_desc = open (infile, file_open_mode); |
| if (input_desc < 0) |
| { |
| error (0, errno, "%s", quotef (infile)); |
| ok = false; |
| continue; |
| } |
| } |
|
|
| if (fstat (input_desc, &stat_buf) < 0) |
| { |
| error (0, errno, "%s", quotef (infile)); |
| ok = false; |
| goto contin; |
| } |
|
|
| |
| idx_t insize = io_blksize (&stat_buf); |
|
|
| fdadvise (input_desc, 0, 0, FADVISE_SEQUENTIAL); |
|
|
| |
| |
| |
|
|
| if (! (S_ISFIFO (stat_buf.st_mode) || S_ISSOCK (stat_buf.st_mode) |
| || S_TYPEISSHM (&stat_buf) || S_TYPEISTMO (&stat_buf)) |
| && have_out_dev |
| && SAME_INODE (stat_buf, out_id)) |
| { |
| off_t in_pos = lseek (input_desc, 0, SEEK_CUR); |
| if (0 <= in_pos) |
| { |
| if (out_flags < -1) |
| out_flags = fcntl (STDOUT_FILENO, F_GETFL); |
| int whence = (0 <= out_flags && out_flags & O_APPEND |
| ? SEEK_END : SEEK_CUR); |
| if (in_pos < lseek (STDOUT_FILENO, 0, whence)) |
| { |
| error (0, 0, _("%s: input file is output file"), |
| quotef (infile)); |
| ok = false; |
| goto contin; |
| } |
| } |
| } |
|
|
| |
| char *inbuf; |
|
|
| |
| |
| |
|
|
| if (! (number || show_ends || show_nonprinting |
| || show_tabs || squeeze_blank)) |
| { |
| int copy_cat_status = |
| out_isreg && S_ISREG (stat_buf.st_mode) ? copy_cat () : 0; |
| if (copy_cat_status != 0) |
| { |
| inbuf = nullptr; |
| ok &= 0 < copy_cat_status; |
| } |
| else |
| { |
| insize = MAX (insize, outsize); |
| inbuf = xalignalloc (page_size, insize); |
| ok &= simple_cat (inbuf, insize); |
| } |
| } |
| else |
| { |
| |
| inbuf = xalignalloc (page_size, insize + 1); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| idx_t bufsize; |
| if (ckd_mul (&bufsize, insize, 4) |
| || ckd_add (&bufsize, bufsize, outsize) |
| || ckd_add (&bufsize, bufsize, LINE_COUNTER_BUF_LEN - 1)) |
| xalloc_die (); |
| char *outbuf = xalignalloc (page_size, bufsize); |
|
|
| ok &= cat (inbuf, insize, outbuf, outsize, show_nonprinting, |
| show_tabs, number, number_nonblank, show_ends, |
| squeeze_blank); |
|
|
| alignfree (outbuf); |
| } |
|
|
| alignfree (inbuf); |
|
|
| contin: |
| if (!reading_stdin && close (input_desc) < 0) |
| { |
| error (0, errno, "%s", quotef (infile)); |
| ok = false; |
| } |
| } |
| while (++argind < argc); |
|
|
| if (pending_cr) |
| { |
| if (full_write (STDOUT_FILENO, "\r", 1) != 1) |
| write_error (); |
| } |
|
|
| if (have_read_stdin && close (STDIN_FILENO) < 0) |
| error (EXIT_FAILURE, errno, _("closing standard input")); |
|
|
| return ok ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |
|
|