summaryrefslogtreecommitdiff
path: root/src/head.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/head.c')
-rw-r--r--src/head.c307
1 files changed, 154 insertions, 153 deletions
diff --git a/src/head.c b/src/head.c
index d79d5f7d..65bd52a1 100644
--- a/src/head.c
+++ b/src/head.c
@@ -1,5 +1,5 @@
/* head -- output first part of file(s)
- Copyright (C) 1989-2013 Free Software Foundation, Inc.
+ Copyright (C) 1989-2014 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
@@ -34,6 +34,7 @@
#include "error.h"
#include "full-read.h"
#include "quote.h"
+#include "quotearg.h"
#include "safe-read.h"
#include "xfreopen.h"
#include "xstrtol.h"
@@ -70,7 +71,6 @@ enum Copy_fd_status
{
COPY_FD_OK = 0,
COPY_FD_READ_ERROR,
- COPY_FD_WRITE_ERROR,
COPY_FD_UNEXPECTED_EOF
};
@@ -147,9 +147,6 @@ diagnose_copy_fd_failure (enum Copy_fd_status err, char const *filename)
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;
@@ -167,11 +164,25 @@ write_header (const char *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. */
+/* Write N_BYTES from BUFFER to stdout.
+ Exit immediately on error with a single diagnostic. */
+
+static void
+xwrite_stdout (char const *buffer, size_t n_bytes)
+{
+ if (n_bytes > 0 && fwrite (buffer, 1, n_bytes, stdout) < n_bytes)
+ {
+ clearerr (stdout); /* To avoid redundant close_stdout diagnostic. */
+ error (EXIT_FAILURE, errno, _("error writing %s"),
+ quote ("standard output"));
+ }
+}
+
+/* Copy no more than N_BYTES from file descriptor SRC_FD to stdout.
+ Return an appropriate indication of success or read failure. */
static enum Copy_fd_status
-copy_fd (int src_fd, FILE *o_stream, uintmax_t n_bytes)
+copy_fd (int src_fd, uintmax_t n_bytes)
{
char buf[BUFSIZ];
const size_t buf_size = sizeof (buf);
@@ -189,14 +200,13 @@ copy_fd (int src_fd, FILE *o_stream, uintmax_t n_bytes)
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;
+ xwrite_stdout (buf, n_read);
}
return COPY_FD_OK;
}
-/* Print all but the last N_ELIDE lines from the input available via
+/* Print all but the last N_ELIDE bytes 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
@@ -282,22 +292,12 @@ elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0)
/* 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);
- }
+ if (! first)
+ xwrite_stdout (b[!i] + READ_BUFSIZE, n_elide - delta);
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;
- }
+ if (n_elide < n_read)
+ xwrite_stdout (b[i], n_read - n_elide);
}
free (b[0]);
@@ -313,18 +313,34 @@ elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0)
size_t n_read;
bool buffered_enough;
size_t i, i_next;
- char **b;
+ char **b = NULL;
/* 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);
+ size_t n_alloc = 0;
+ size_t n_array_alloc = 0;
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);
+ if (n_array_alloc == i)
+ {
+ /* reallocate between 16 and n_bufs entries. */
+ if (n_array_alloc == 0)
+ n_array_alloc = MIN (n_bufs, 16);
+ else if (n_array_alloc <= n_bufs / 2)
+ n_array_alloc *= 2;
+ else
+ n_array_alloc = n_bufs;
+ b = xnrealloc (b, n_array_alloc, sizeof *b);
+ }
+
+ if (! buffered_enough)
+ {
+ b[i] = xmalloc (READ_BUFSIZE);
+ n_alloc = i + 1;
+ }
n_read = full_read (fd, b[i], READ_BUFSIZE);
if (n_read < READ_BUFSIZE)
{
@@ -341,14 +357,7 @@ elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0)
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;
- }
- }
+ xwrite_stdout (b[i_next], n_read);
}
/* Output any remainder: rem bytes from b[i] + n_read. */
@@ -359,12 +368,12 @@ elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0)
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);
+ xwrite_stdout (b[i] + n_read, rem);
}
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);
+ xwrite_stdout (b[i] + n_read, n_bytes_left_in_b_i);
+ xwrite_stdout (b[i_next], rem - n_bytes_left_in_b_i);
}
}
else if (i + 1 == n_bufs)
@@ -383,12 +392,12 @@ elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0)
*/
size_t y = READ_BUFSIZE - rem;
size_t x = n_read - y;
- fwrite (b[i_next], 1, x, stdout);
+ xwrite_stdout (b[i_next], x);
}
}
free_mem:
- for (i = 0; i < n_bufs; i++)
+ for (i = 0; i < n_alloc; i++)
free (b[i]);
free (b);
@@ -396,53 +405,53 @@ elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0)
}
}
-/* Print all but the last N_ELIDE lines from the input available
- via file descriptor FD. Return true upon success.
+/* Call lseek (FD, OFFSET, WHENCE), where file descriptor FD
+ corresponds to the file FILENAME. WHENCE must be SEEK_SET or
+ SEEK_CUR. Return the resulting offset. Give a diagnostic and
+ return -1 if lseek fails. */
+
+static off_t
+elseek (int fd, off_t offset, int whence, char const *filename)
+{
+ off_t new_offset = lseek (fd, offset, whence);
+ char buf[INT_BUFSIZE_BOUND (offset)];
+
+ if (new_offset < 0)
+ error (0, errno,
+ _(whence == SEEK_SET
+ ? N_("%s: cannot seek to offset %s")
+ : N_("%s: cannot seek to relative offset %s")),
+ quotearg_colon (filename),
+ offtostr (offset, buf));
+
+ return new_offset;
+}
+
+/* For the file FILENAME with descriptor FD, output all but the last N_ELIDE
+ bytes. If SIZE is nonnegative, this is a regular file positioned
+ at START_POS with SIZE bytes. Return true on 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)
+elide_tail_bytes_file (const char *filename, int fd, uintmax_t n_elide,
+ off_t current_pos, off_t size)
{
- struct stat stats;
-
- if (presume_input_pipe || fstat (fd, &stats) || ! S_ISREG (stats.st_mode))
- {
- return elide_tail_bytes_pipe (filename, fd, n_elide);
- }
+ if (size < 0)
+ 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, 0, SEEK_CUR)) == -1
- || (end_pos = lseek (fd, 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;
+ off_t diff = size - current_pos;
+ off_t bytes_remaining = diff < 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, 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);
+ enum Copy_fd_status err = copy_fd (fd, bytes_remaining - n_elide);
if (err == COPY_FD_OK)
return true;
@@ -485,6 +494,13 @@ elide_tail_lines_pipe (const char *filename, int fd, uintmax_t n_elide)
n_read = safe_read (fd, tmp->buffer, BUFSIZ);
if (n_read == 0 || n_read == SAFE_READ_ERROR)
break;
+
+ if (! n_elide)
+ {
+ xwrite_stdout (tmp->buffer, n_read);
+ continue;
+ }
+
tmp->nbytes = n_read;
tmp->nlines = 0;
tmp->next = NULL;
@@ -520,7 +536,7 @@ elide_tail_lines_pipe (const char *filename, int fd, uintmax_t n_elide)
last = last->next = tmp;
if (n_elide < total_lines - first->nlines)
{
- fwrite (first->buffer, 1, first->nbytes, stdout);
+ xwrite_stdout (first->buffer, first->nbytes);
tmp = first;
total_lines -= first->nlines;
first = first->next;
@@ -549,7 +565,7 @@ elide_tail_lines_pipe (const char *filename, int fd, uintmax_t n_elide)
for (tmp = first; n_elide < total_lines - tmp->nlines; tmp = tmp->next)
{
- fwrite (tmp->buffer, 1, tmp->nbytes, stdout);
+ xwrite_stdout (tmp->buffer, tmp->nbytes);
total_lines -= tmp->nlines;
}
@@ -565,7 +581,7 @@ elide_tail_lines_pipe (const char *filename, int fd, uintmax_t n_elide)
++tmp->nlines;
--n;
}
- fwrite (tmp->buffer, 1, p - tmp->buffer, stdout);
+ xwrite_stdout (tmp->buffer, p - tmp->buffer);
}
free_lbuffers:
@@ -579,10 +595,10 @@ free_lbuffers:
}
/* Output all but the last N_LINES lines of the input stream defined by
- FD, START_POS, and END_POS.
+ FD, START_POS, and SIZE.
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).
+ SIZE is the file size in bytes.
Return true upon success.
Give a diagnostic and return false upon error.
@@ -592,11 +608,11 @@ free_lbuffers:
static bool
elide_tail_lines_seekable (const char *pretty_filename, int fd,
uintmax_t n_lines,
- off_t start_pos, off_t end_pos)
+ off_t start_pos, off_t size)
{
char buffer[BUFSIZ];
size_t bytes_read;
- off_t pos = end_pos;
+ off_t pos = size;
/* Set 'bytes_read' to the size of the last, probably partial, buffer;
0 < 'bytes_read' <= 'BUFSIZ'. */
@@ -606,13 +622,8 @@ elide_tail_lines_seekable (const char *pretty_filename, int fd,
/* 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 (pos)];
- error (0, errno, _("%s: cannot seek to offset %s"),
- pretty_filename, offtostr (pos, offset_buf));
- return false;
- }
+ if (elseek (fd, pos, SEEK_SET, pretty_filename) < 0)
+ return false;
bytes_read = safe_read (fd, buffer, bytes_read);
if (bytes_read == SAFE_READ_ERROR)
{
@@ -620,8 +631,11 @@ elide_tail_lines_seekable (const char *pretty_filename, int fd,
return false;
}
+ /* n_lines == 0 case needs special treatment. */
+ const bool all_lines = !n_lines;
+
/* Count the incomplete line on files that don't end with a newline. */
- if (bytes_read && buffer[bytes_read - 1] != '\n')
+ if (n_lines && bytes_read && buffer[bytes_read - 1] != '\n')
--n_lines;
while (1)
@@ -631,11 +645,16 @@ elide_tail_lines_seekable (const char *pretty_filename, int fd,
size_t n = bytes_read;
while (n)
{
- char const *nl;
- nl = memrchr (buffer, '\n', n);
- if (nl == NULL)
- break;
- n = nl - buffer;
+ if (all_lines)
+ n -= 1;
+ else
+ {
+ char const *nl;
+ nl = memrchr (buffer, '\n', n);
+ if (nl == NULL)
+ break;
+ n = nl - buffer;
+ }
if (n_lines-- == 0)
{
/* Found it. */
@@ -644,16 +663,10 @@ elide_tail_lines_seekable (const char *pretty_filename, int fd,
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;
- }
+ if (elseek (fd, start_pos, SEEK_SET, pretty_filename) < 0)
+ return false;
- err = copy_fd (fd, stdout, pos - start_pos);
+ err = copy_fd (fd, pos - start_pos);
if (err != COPY_FD_OK)
{
diagnose_copy_fd_failure (err, pretty_filename);
@@ -662,19 +675,11 @@ elide_tail_lines_seekable (const char *pretty_filename, int fd,
}
/* 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);
+ in which we found the desired newline byte. */
+ xwrite_stdout (buffer, n + 1);
/* Set file pointer to the byte after what we've output. */
- if (lseek (fd, pos + n + 1, SEEK_SET) < 0)
- {
- error (0, errno, _("%s: failed to reset file pointer"),
- quote (pretty_filename));
- return false;
- }
- return true;
+ return 0 <= elseek (fd, pos + n + 1, SEEK_SET, pretty_filename);
}
}
@@ -685,13 +690,8 @@ elide_tail_lines_seekable (const char *pretty_filename, int fd,
return true;
}
pos -= BUFSIZ;
- if (lseek (fd, pos, SEEK_SET) < 0)
- {
- char offset_buf[INT_BUFSIZE_BOUND (pos)];
- error (0, errno, _("%s: cannot seek to offset %s"),
- pretty_filename, offtostr (pos, offset_buf));
- return false;
- }
+ if (elseek (fd, pos, SEEK_SET, pretty_filename) < 0)
+ return false;
bytes_read = safe_read (fd, buffer, BUFSIZ);
if (bytes_read == SAFE_READ_ERROR)
@@ -707,37 +707,28 @@ elide_tail_lines_seekable (const char *pretty_filename, int fd,
}
}
-/* Print all but the last N_ELIDE lines from the input available
- via file descriptor FD. Return true upon success.
+/* For the file FILENAME with descriptor FD, output all but the last N_ELIDE
+ lines. If SIZE is nonnegative, this is a regular file positioned
+ at START_POS with SIZE bytes. Return true on success.
Give a diagnostic and return nonzero upon error. */
static bool
-elide_tail_lines_file (const char *filename, int fd, uintmax_t n_elide)
+elide_tail_lines_file (const char *filename, int fd, uintmax_t n_elide,
+ off_t current_pos, off_t size)
{
- if (!presume_input_pipe)
+ if (size < 0)
+ return elide_tail_lines_pipe (filename, fd, n_elide);
+ else
{
/* 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, 0, SEEK_CUR);
- off_t end_pos = lseek (fd, 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 (size <= current_pos
+ || elide_tail_lines_seekable (filename, fd, n_elide,
+ current_pos, size));
}
-
- return elide_tail_lines_pipe (filename, fd, n_elide);
}
static bool
@@ -759,8 +750,7 @@ head_bytes (const char *filename, int fd, uintmax_t bytes_to_write)
}
if (bytes_read == 0)
break;
- if (fwrite (buffer, 1, bytes_read, stdout) < bytes_read)
- error (EXIT_FAILURE, errno, _("write error"));
+ xwrite_stdout (buffer, bytes_read);
bytes_to_write -= bytes_read;
}
return true;
@@ -792,16 +782,13 @@ head_lines (const char *filename, int fd, uintmax_t lines_to_write)
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));
+ elseek (fd, -n_bytes_past_EOL, SEEK_CUR, filename);
}
break;
}
- if (fwrite (buffer, 1, bytes_to_write, stdout) < bytes_to_write)
- error (EXIT_FAILURE, errno, _("write error"));
+ xwrite_stdout (buffer, bytes_to_write);
}
return true;
}
@@ -815,14 +802,28 @@ head (const char *filename, int fd, uintmax_t n_units, bool count_lines,
if (elide_from_end)
{
- if (count_lines)
+ off_t current_pos = -1, size = -1;
+ if (! presume_input_pipe)
{
- return elide_tail_lines_file (filename, fd, n_units);
+ struct stat st;
+ if (fstat (fd, &st) != 0)
+ {
+ error (0, errno, _("cannot fstat %s"),
+ quotearg_colon (filename));
+ return false;
+ }
+ if (S_ISREG (st.st_mode))
+ {
+ size = st.st_size;
+ current_pos = elseek (fd, 0, SEEK_CUR, filename);
+ if (current_pos < 0)
+ return false;
+ }
}
+ if (count_lines)
+ return elide_tail_lines_file (filename, fd, n_units, current_pos, size);
else
- {
- return elide_tail_bytes_file (filename, fd, n_units);
- }
+ return elide_tail_bytes_file (filename, fd, n_units, current_pos, size);
}
if (count_lines)
return head_lines (filename, fd, n_units);