diff options
Diffstat (limited to 'src/head.c')
-rw-r--r-- | src/head.c | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/src/head.c b/src/head.c new file mode 100644 index 0000000..4038722 --- /dev/null +++ b/src/head.c @@ -0,0 +1,1064 @@ +/* head -- output first part of file(s) + Copyright (C) 89, 90, 91, 1995-2006 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Options: (see usage) + Reads from standard input if no files are given or when a filename of + ``-'' is encountered. + By default, filename headers are printed only if more than one file + is given. + By default, prints the first 10 lines (head -n 10). + + David MacKenzie <djm@gnu.ai.mit.edu> */ + +#include <config.h> + +#include <stdio.h> +#include <getopt.h> +#include <sys/types.h> + +#include "system.h" + +#include "error.h" +#include "full-write.h" +#include "full-read.h" +#include "inttostr.h" +#include "quote.h" +#include "safe-read.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "head" + +#define AUTHORS "David MacKenzie", "Jim Meyering" + +/* Number of lines/chars/blocks to head. */ +#define DEFAULT_NUMBER 10 + +/* Useful only when eliding tail bytes or lines. + If true, skip the is-regular-file test used to determine whether + to use the lseek optimization. Instead, use the more general (and + more expensive) code unconditionally. Intended solely for testing. */ +static bool presume_input_pipe; + +/* If true, print filename headers. */ +static bool print_headers; + +/* When to print the filename banners. */ +enum header_mode +{ + multiple_files, always, never +}; + +/* The name this program was run with. */ +char *program_name; + +/* Have we ever read standard input? */ +static bool have_read_stdin; + +enum Copy_fd_status + { + COPY_FD_OK = 0, + COPY_FD_READ_ERROR, + COPY_FD_WRITE_ERROR, + COPY_FD_UNEXPECTED_EOF + }; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + PRESUME_INPUT_PIPE_OPTION = CHAR_MAX + 1 +}; + +static struct option const long_options[] = +{ + {"bytes", required_argument, NULL, 'c'}, + {"lines", required_argument, NULL, 'n'}, + {"-presume-input-pipe", no_argument, NULL, + PRESUME_INPUT_PIPE_OPTION}, /* do not document */ + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 'q'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Print the first 10 lines of each FILE to standard output.\n\ +With more than one FILE, precede each with a header giving the file name.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -c, --bytes=[-]N print the first N bytes of each file;\n\ + with the leading `-', print all but the last\n\ + N bytes of each file\n\ + -n, --lines=[-]N print the first N lines instead of the first 10;\n\ + with the leading `-', print all but the last\n\ + N lines of each file\n\ +"), stdout); + fputs (_("\ + -q, --quiet, --silent never print headers giving file names\n\ + -v, --verbose always print headers giving file names\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +N may have a multiplier suffix: b 512, k 1024, m 1024*1024.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static void +diagnose_copy_fd_failure (enum Copy_fd_status err, char const *filename) +{ + switch (err) + { + case COPY_FD_READ_ERROR: + error (0, errno, _("error reading %s"), quote (filename)); + break; + case COPY_FD_WRITE_ERROR: + error (0, errno, _("error writing %s"), quote (filename)); + break; + case COPY_FD_UNEXPECTED_EOF: + error (0, errno, _("%s: file has shrunk too much"), quote (filename)); + break; + default: + abort (); + } +} + +static void +write_header (const char *filename) +{ + static bool first_file = true; + + printf ("%s==> %s <==\n", (first_file ? "" : "\n"), filename); + first_file = false; +} + +/* Copy no more than N_BYTES from file descriptor SRC_FD to O_STREAM. + Return an appropriate indication of success or failure. */ + +static enum Copy_fd_status +copy_fd (int src_fd, FILE *o_stream, uintmax_t n_bytes) +{ + char buf[BUFSIZ]; + const size_t buf_size = sizeof (buf); + + /* Copy the file contents. */ + while (0 < n_bytes) + { + size_t n_to_read = MIN (buf_size, n_bytes); + size_t n_read = safe_read (src_fd, buf, n_to_read); + if (n_read == SAFE_READ_ERROR) + return COPY_FD_READ_ERROR; + + n_bytes -= n_read; + + if (n_read == 0 && n_bytes != 0) + return COPY_FD_UNEXPECTED_EOF; + + if (fwrite (buf, 1, n_read, o_stream) < n_read) + return COPY_FD_WRITE_ERROR; + } + + return COPY_FD_OK; +} + +/* Print all but the last N_ELIDE lines from the input available via + the non-seekable file descriptor FD. Return true upon success. + Give a diagnostic and return false upon error. */ +static bool +elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0) +{ + size_t n_elide = n_elide_0; + +#ifndef HEAD_TAIL_PIPE_READ_BUFSIZE +# define HEAD_TAIL_PIPE_READ_BUFSIZE BUFSIZ +#endif +#define READ_BUFSIZE HEAD_TAIL_PIPE_READ_BUFSIZE + + /* If we're eliding no more than this many bytes, then it's ok to allocate + more memory in order to use a more time-efficient algorithm. + FIXME: use a fraction of available memory instead, as in sort. + FIXME: is this even worthwhile? */ +#ifndef HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD +# define HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD 1024 * 1024 +#endif + +#if HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD < 2 * READ_BUFSIZE + "HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD must be at least 2 * READ_BUFSIZE" +#endif + + if (SIZE_MAX < n_elide_0 + READ_BUFSIZE) + { + char umax_buf[INT_BUFSIZE_BOUND (uintmax_t)]; + error (EXIT_FAILURE, 0, _("%s: number of bytes is too large"), + umaxtostr (n_elide_0, umax_buf)); + } + + /* Two cases to consider... + 1) n_elide is small enough that we can afford to double-buffer: + allocate 2 * (READ_BUFSIZE + n_elide) bytes + 2) n_elide is too big for that, so we allocate only + (READ_BUFSIZE + n_elide) bytes + + FIXME: profile, to see if double-buffering is worthwhile + + CAUTION: do not fail (out of memory) when asked to elide + a ridiculous amount, but when given only a small input. */ + + if (n_elide <= HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD) + { + bool ok = true; + bool first = true; + bool eof = false; + size_t n_to_read = READ_BUFSIZE + n_elide; + bool i; + char *b[2]; + b[0] = xnmalloc (2, n_to_read); + b[1] = b[0] + n_to_read; + + for (i = false; ! eof ; i = !i) + { + size_t n_read = full_read (fd, b[i], n_to_read); + size_t delta = 0; + if (n_read < n_to_read) + { + if (errno != 0) + { + error (0, errno, _("error reading %s"), quote (filename)); + ok = false; + break; + } + + /* reached EOF */ + if (n_read <= n_elide) + { + if (first) + { + /* The input is no larger than the number of bytes + to elide. So there's nothing to output, and + we're done. */ + } + else + { + delta = n_elide - n_read; + } + } + eof = true; + } + + /* Output any (but maybe just part of the) elided data from + the previous round. */ + if ( ! first) + { + /* Don't bother checking for errors here. + If there's a failure, the test of the following + fwrite or in close_stdout will catch it. */ + fwrite (b[!i] + READ_BUFSIZE, 1, n_elide - delta, stdout); + } + first = false; + + if (n_elide < n_read + && fwrite (b[i], 1, n_read - n_elide, stdout) < n_read - n_elide) + { + error (0, errno, _("write error")); + ok = false; + break; + } + } + + free (b[0]); + return ok; + } + else + { + /* Read blocks of size READ_BUFSIZE, until we've read at least n_elide + bytes. Then, for each new buffer we read, also write an old one. */ + + bool ok = true; + bool eof = false; + size_t n_read; + bool buffered_enough; + size_t i, i_next; + char **b; + /* Round n_elide up to a multiple of READ_BUFSIZE. */ + size_t rem = READ_BUFSIZE - (n_elide % READ_BUFSIZE); + size_t n_elide_round = n_elide + rem; + size_t n_bufs = n_elide_round / READ_BUFSIZE + 1; + b = xcalloc (n_bufs, sizeof *b); + + buffered_enough = false; + for (i = 0, i_next = 1; !eof; i = i_next, i_next = (i_next + 1) % n_bufs) + { + if (b[i] == NULL) + b[i] = xmalloc (READ_BUFSIZE); + n_read = full_read (fd, b[i], READ_BUFSIZE); + if (n_read < READ_BUFSIZE) + { + if (errno != 0) + { + error (0, errno, _("error reading %s"), quote (filename)); + ok = false; + goto free_mem; + } + eof = true; + } + + if (i + 1 == n_bufs) + buffered_enough = true; + + if (buffered_enough) + { + if (fwrite (b[i_next], 1, n_read, stdout) < n_read) + { + error (0, errno, _("write error")); + ok = false; + goto free_mem; + } + } + } + + /* Output any remainder: rem bytes from b[i] + n_read. */ + if (rem) + { + if (buffered_enough) + { + size_t n_bytes_left_in_b_i = READ_BUFSIZE - n_read; + if (rem < n_bytes_left_in_b_i) + { + fwrite (b[i] + n_read, 1, rem, stdout); + } + else + { + fwrite (b[i] + n_read, 1, n_bytes_left_in_b_i, stdout); + fwrite (b[i_next], 1, rem - n_bytes_left_in_b_i, stdout); + } + } + else if (i + 1 == n_bufs) + { + /* This happens when n_elide < file_size < n_elide_round. + + |READ_BUF.| + | | rem | + |---------!---------!---------!---------| + |---- n_elide ---------| + | | x | + | |y | + |---- file size -----------| + | |n_read| + |---- n_elide_round ----------| + */ + size_t y = READ_BUFSIZE - rem; + size_t x = n_read - y; + fwrite (b[i_next], 1, x, stdout); + } + } + + free_mem:; + for (i = 0; i < n_bufs; i++) + free (b[i]); + free (b); + + return ok; + } +} + +/* Print all but the last N_ELIDE lines from the input available + via file descriptor FD. Return true upon success. + Give a diagnostic and return false upon error. */ + +/* NOTE: if the input file shrinks by more than N_ELIDE bytes between + the length determination and the actual reading, then head fails. */ + +static bool +elide_tail_bytes_file (const char *filename, int fd, uintmax_t n_elide) +{ + struct stat stats; + + if (presume_input_pipe || fstat (fd, &stats) || ! S_ISREG (stats.st_mode)) + { + return elide_tail_bytes_pipe (filename, fd, n_elide); + } + else + { + off_t current_pos, end_pos; + uintmax_t bytes_remaining; + off_t diff; + enum Copy_fd_status err; + + if ((current_pos = lseek (fd, (off_t) 0, SEEK_CUR)) == -1 + || (end_pos = lseek (fd, (off_t) 0, SEEK_END)) == -1) + { + error (0, errno, _("cannot lseek %s"), quote (filename)); + return false; + } + + /* Be careful here. The current position may actually be + beyond the end of the file. */ + bytes_remaining = (diff = end_pos - current_pos) < 0 ? 0 : diff; + + if (bytes_remaining <= n_elide) + return true; + + /* Seek back to `current' position, then copy the required + number of bytes from fd. */ + if (lseek (fd, (off_t) 0, current_pos) == -1) + { + error (0, errno, _("%s: cannot lseek back to original position"), + quote (filename)); + return false; + } + + err = copy_fd (fd, stdout, bytes_remaining - n_elide); + if (err == COPY_FD_OK) + return true; + + diagnose_copy_fd_failure (err, filename); + return false; + } +} + +/* Print all but the last N_ELIDE lines from the input stream + open for reading via file descriptor FD. + Buffer the specified number of lines as a linked list of LBUFFERs, + adding them as needed. Return true if successful. */ + +static bool +elide_tail_lines_pipe (const char *filename, int fd, uintmax_t n_elide) +{ + struct linebuffer + { + char buffer[BUFSIZ]; + size_t nbytes; + size_t nlines; + struct linebuffer *next; + }; + typedef struct linebuffer LBUFFER; + LBUFFER *first, *last, *tmp; + size_t total_lines = 0; /* Total number of newlines in all buffers. */ + bool ok = true; + size_t n_read; /* Size in bytes of most recent read */ + + first = last = xmalloc (sizeof (LBUFFER)); + first->nbytes = first->nlines = 0; + first->next = NULL; + tmp = xmalloc (sizeof (LBUFFER)); + + /* Always read into a fresh buffer. + Read, (producing no output) until we've accumulated at least + n_elide newlines, or until EOF, whichever comes first. */ + while (1) + { + n_read = safe_read (fd, tmp->buffer, BUFSIZ); + if (n_read == 0 || n_read == SAFE_READ_ERROR) + break; + tmp->nbytes = n_read; + tmp->nlines = 0; + tmp->next = NULL; + + /* Count the number of newlines just read. */ + { + char const *buffer_end = tmp->buffer + n_read; + char const *p = tmp->buffer; + while ((p = memchr (p, '\n', buffer_end - p))) + { + ++p; + ++tmp->nlines; + } + } + total_lines += tmp->nlines; + + /* If there is enough room in the last buffer read, just append the new + one to it. This is because when reading from a pipe, `n_read' can + often be very small. */ + if (tmp->nbytes + last->nbytes < BUFSIZ) + { + memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes); + last->nbytes += tmp->nbytes; + last->nlines += tmp->nlines; + } + else + { + /* If there's not enough room, link the new buffer onto the end of + the list, then either free up the oldest buffer for the next + read if that would leave enough lines, or else malloc a new one. + Some compaction mechanism is possible but probably not + worthwhile. */ + last = last->next = tmp; + if (n_elide < total_lines - first->nlines) + { + fwrite (first->buffer, 1, first->nbytes, stdout); + tmp = first; + total_lines -= first->nlines; + first = first->next; + } + else + tmp = xmalloc (sizeof (LBUFFER)); + } + } + + free (tmp); + + if (n_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (filename)); + ok = false; + goto free_lbuffers; + } + + /* If we read any bytes at all, count the incomplete line + on files that don't end with a newline. */ + if (last->nbytes && last->buffer[last->nbytes - 1] != '\n') + { + ++last->nlines; + ++total_lines; + } + + for (tmp = first; n_elide < total_lines - tmp->nlines; tmp = tmp->next) + { + fwrite (tmp->buffer, 1, tmp->nbytes, stdout); + total_lines -= tmp->nlines; + } + + /* Print the first `total_lines - n_elide' lines of tmp->buffer. */ + if (n_elide < total_lines) + { + size_t n = total_lines - n_elide; + char const *buffer_end = tmp->buffer + tmp->nbytes; + char const *p = tmp->buffer; + while (n && (p = memchr (p, '\n', buffer_end - p))) + { + ++p; + ++tmp->nlines; + --n; + } + fwrite (tmp->buffer, 1, p - tmp->buffer, stdout); + } + +free_lbuffers: + while (first) + { + tmp = first->next; + free (first); + first = tmp; + } + return ok; +} + +/* Output all but the last N_LINES lines of the input stream defined by + FD, START_POS, and END_POS. + START_POS is the starting position of the read pointer for the file + associated with FD (may be nonzero). + END_POS is the file offset of EOF (one larger than offset of last byte). + Return true upon success. + Give a diagnostic and return false upon error. + + NOTE: this code is very similar to that of tail.c's file_lines function. + Unfortunately, factoring out some common core looks like it'd result + in a less efficient implementation or a messy interface. */ +static bool +elide_tail_lines_seekable (const char *pretty_filename, int fd, + uintmax_t n_lines, + off_t start_pos, off_t end_pos) +{ + char buffer[BUFSIZ]; + size_t bytes_read; + off_t pos = end_pos; + + /* Set `bytes_read' to the size of the last, probably partial, buffer; + 0 < `bytes_read' <= `BUFSIZ'. */ + bytes_read = (pos - start_pos) % BUFSIZ; + if (bytes_read == 0) + bytes_read = BUFSIZ; + /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all + reads will be on block boundaries, which might increase efficiency. */ + pos -= bytes_read; + if (lseek (fd, pos, SEEK_SET) < 0) + { + char offset_buf[INT_BUFSIZE_BOUND (off_t)]; + error (0, errno, _("%s: cannot seek to offset %s"), + pretty_filename, offtostr (pos, offset_buf)); + return false; + } + bytes_read = safe_read (fd, buffer, bytes_read); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return false; + } + + /* Count the incomplete line on files that don't end with a newline. */ + if (bytes_read && buffer[bytes_read - 1] != '\n') + --n_lines; + + while (1) + { + /* Scan backward, counting the newlines in this bufferfull. */ + + size_t n = bytes_read; + while (n) + { + char const *nl; + nl = memrchr (buffer, '\n', n); + if (nl == NULL) + break; + n = nl - buffer; + if (n_lines-- == 0) + { + /* Found it. */ + /* If necessary, restore the file pointer and copy + input to output up to position, POS. */ + if (start_pos < pos) + { + enum Copy_fd_status err; + if (lseek (fd, start_pos, SEEK_SET) < 0) + { + /* Failed to reposition file pointer. */ + error (0, errno, + "%s: unable to restore file pointer to initial offset", + quote (pretty_filename)); + return false; + } + + err = copy_fd (fd, stdout, pos - start_pos); + if (err != COPY_FD_OK) + { + diagnose_copy_fd_failure (err, pretty_filename); + return false; + } + } + + /* Output the initial portion of the buffer + in which we found the desired newline byte. + Don't bother testing for failure for such a small amount. + Any failure will be detected upon close. */ + fwrite (buffer, 1, n + 1, stdout); + return true; + } + } + + /* Not enough newlines in that bufferfull. */ + if (pos == start_pos) + { + /* Not enough lines in the file. */ + return true; + } + pos -= BUFSIZ; + if (lseek (fd, pos, SEEK_SET) < 0) + { + char offset_buf[INT_BUFSIZE_BOUND (off_t)]; + error (0, errno, _("%s: cannot seek to offset %s"), + pretty_filename, offtostr (pos, offset_buf)); + return false; + } + + bytes_read = safe_read (fd, buffer, BUFSIZ); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return false; + } + + /* FIXME: is this dead code? + Consider the test, pos == start_pos, above. */ + if (bytes_read == 0) + return true; + } +} + +/* Print all but the last N_ELIDE lines from the input available + via file descriptor FD. Return true upon success. + Give a diagnostic and return nonzero upon error. */ + +static bool +elide_tail_lines_file (const char *filename, int fd, uintmax_t n_elide) +{ + if (!presume_input_pipe) + { + /* Find the offset, OFF, of the Nth newline from the end, + but not counting the last byte of the file. + If found, write from current position to OFF, inclusive. + Otherwise, just return true. */ + + off_t start_pos = lseek (fd, (off_t) 0, SEEK_CUR); + off_t end_pos = lseek (fd, (off_t) 0, SEEK_END); + if (0 <= start_pos && start_pos < end_pos) + { + /* If the file is empty, we're done. */ + if (end_pos == 0) + return true; + + return elide_tail_lines_seekable (filename, fd, n_elide, + start_pos, end_pos); + } + + /* lseek failed or the end offset precedes start. + Fall through. */ + } + + return elide_tail_lines_pipe (filename, fd, n_elide); +} + +static bool +head_bytes (const char *filename, int fd, uintmax_t bytes_to_write) +{ + char buffer[BUFSIZ]; + size_t bytes_to_read = BUFSIZ; + + while (bytes_to_write) + { + size_t bytes_read; + if (bytes_to_write < bytes_to_read) + bytes_to_read = bytes_to_write; + bytes_read = safe_read (fd, buffer, bytes_to_read); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (filename)); + return false; + } + if (bytes_read == 0) + break; + if (fwrite (buffer, 1, bytes_read, stdout) < bytes_read) + error (EXIT_FAILURE, errno, _("write error")); + bytes_to_write -= bytes_read; + } + return true; +} + +static bool +head_lines (const char *filename, int fd, uintmax_t lines_to_write) +{ + char buffer[BUFSIZ]; + + while (lines_to_write) + { + size_t bytes_read = safe_read (fd, buffer, BUFSIZ); + size_t bytes_to_write = 0; + + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (filename)); + return false; + } + if (bytes_read == 0) + break; + while (bytes_to_write < bytes_read) + if (buffer[bytes_to_write++] == '\n' && --lines_to_write == 0) + { + off_t n_bytes_past_EOL = bytes_read - bytes_to_write; + /* If we have read more data than that on the specified number + of lines, try to seek back to the position we would have + gotten to had we been reading one byte at a time. */ + if (lseek (fd, -n_bytes_past_EOL, SEEK_CUR) < 0) + { + int e = errno; + struct stat st; + if (fstat (fd, &st) != 0 || S_ISREG (st.st_mode)) + error (0, e, _("cannot reposition file pointer for %s"), + quote (filename)); + } + break; + } + if (fwrite (buffer, 1, bytes_to_write, stdout) < bytes_to_write) + error (EXIT_FAILURE, errno, _("write error")); + } + return true; +} + +static bool +head (const char *filename, int fd, uintmax_t n_units, bool count_lines, + bool elide_from_end) +{ + if (print_headers) + write_header (filename); + + if (elide_from_end) + { + if (count_lines) + { + return elide_tail_lines_file (filename, fd, n_units); + } + else + { + return elide_tail_bytes_file (filename, fd, n_units); + } + } + if (count_lines) + return head_lines (filename, fd, n_units); + else + return head_bytes (filename, fd, n_units); +} + +static bool +head_file (const char *filename, uintmax_t n_units, bool count_lines, + bool elide_from_end) +{ + int fd; + bool ok; + bool is_stdin = STREQ (filename, "-"); + + if (is_stdin) + { + have_read_stdin = true; + fd = STDIN_FILENO; + filename = _("standard input"); + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + fd = open (filename, O_RDONLY | O_BINARY); + if (fd < 0) + { + error (0, errno, _("cannot open %s for reading"), quote (filename)); + return false; + } + } + + ok = head (filename, fd, n_units, count_lines, elide_from_end); + if (!is_stdin && close (fd) != 0) + { + error (0, errno, _("closing %s"), quote (filename)); + return false; + } + return ok; +} + +/* Convert a string of decimal digits, N_STRING, with a single, optional suffix + character (b, k, or m) to an integral value. Upon successful conversion, + return that value. If it cannot be converted, give a diagnostic and exit. + COUNT_LINES indicates whether N_STRING is a number of bytes or a number + of lines. It is used solely to give a more specific diagnostic. */ + +static uintmax_t +string_to_integer (bool count_lines, const char *n_string) +{ + strtol_error s_err; + uintmax_t n; + + s_err = xstrtoumax (n_string, NULL, 10, &n, "bkm"); + + if (s_err == LONGINT_OVERFLOW) + { + error (EXIT_FAILURE, 0, + _("%s: %s is so large that it is not representable"), n_string, + count_lines ? _("number of lines") : _("number of bytes")); + } + + if (s_err != LONGINT_OK) + { + error (EXIT_FAILURE, 0, "%s: %s", n_string, + (count_lines + ? _("invalid number of lines") + : _("invalid number of bytes"))); + } + + return n; +} + +int +main (int argc, char **argv) +{ + enum header_mode header_mode = multiple_files; + bool ok = true; + int c; + size_t i; + + /* Number of items to print. */ + uintmax_t n_units = DEFAULT_NUMBER; + + /* If true, interpret the numeric argument as the number of lines. + Otherwise, interpret it as the number of bytes. */ + bool count_lines = true; + + /* Elide the specified number of lines or bytes, counting from + the end of the file. */ + bool elide_from_end = false; + + /* Initializer for file_list if no file-arguments + were specified on the command line. */ + static char const *const default_file_list[] = {"-", NULL}; + char const *const *file_list; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + + print_headers = false; + + if (1 < argc && argv[1][0] == '-' && ISDIGIT (argv[1][1])) + { + char *a = argv[1]; + char *n_string = ++a; + char *end_n_string; + char multiplier_char = 0; + + /* Old option syntax; a dash, one or more digits, and one or + more option letters. Move past the number. */ + do ++a; + while (ISDIGIT (*a)); + + /* Pointer to the byte after the last digit. */ + end_n_string = a; + + /* Parse any appended option letters. */ + for (; *a; a++) + { + switch (*a) + { + case 'c': + count_lines = false; + multiplier_char = 0; + break; + + case 'b': + case 'k': + case 'm': + count_lines = false; + multiplier_char = *a; + break; + + case 'l': + count_lines = true; + break; + + case 'q': + header_mode = never; + break; + + case 'v': + header_mode = always; + break; + + default: + error (0, 0, _("invalid trailing option -- %c"), *a); + usage (EXIT_FAILURE); + } + } + + /* Append the multiplier character (if any) onto the end of + the digit string. Then add NUL byte if necessary. */ + *end_n_string = multiplier_char; + if (multiplier_char) + *(++end_n_string) = 0; + + n_units = string_to_integer (count_lines, n_string); + + /* Make the options we just parsed invisible to getopt. */ + argv[1] = argv[0]; + argv++; + argc--; + } + + while ((c = getopt_long (argc, argv, "c:n:qv0123456789", long_options, NULL)) + != -1) + { + switch (c) + { + case PRESUME_INPUT_PIPE_OPTION: + presume_input_pipe = true; + break; + + case 'c': + count_lines = false; + elide_from_end = (*optarg == '-'); + if (elide_from_end) + ++optarg; + n_units = string_to_integer (count_lines, optarg); + break; + + case 'n': + count_lines = true; + elide_from_end = (*optarg == '-'); + if (elide_from_end) + ++optarg; + n_units = string_to_integer (count_lines, optarg); + break; + + case 'q': + header_mode = never; + break; + + case 'v': + header_mode = always; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + if (ISDIGIT (c)) + error (0, 0, _("invalid trailing option -- %c"), c); + usage (EXIT_FAILURE); + } + } + + if (header_mode == always + || (header_mode == multiple_files && optind < argc - 1)) + print_headers = true; + + if ( ! count_lines && elide_from_end && OFF_T_MAX < n_units) + { + char umax_buf[INT_BUFSIZE_BOUND (uintmax_t)]; + error (EXIT_FAILURE, 0, _("%s: number of bytes is too large"), + umaxtostr (n_units, umax_buf)); + } + + file_list = (optind < argc + ? (char const *const *) &argv[optind] + : default_file_list); + + if (O_BINARY && ! isatty (STDOUT_FILENO)) + freopen (NULL, "wb", stdout); + + for (i = 0; file_list[i]; ++i) + ok &= head_file (file_list[i], n_units, count_lines, elide_from_end); + + if (have_read_stdin && close (STDIN_FILENO) < 0) + error (EXIT_FAILURE, errno, "-"); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} |