/* tac - concatenate and print files in reverse
- Copyright (C) 1988-1991, 1995-2006, 2008-2011 Free Software Foundation, Inc.
+ Copyright (C) 1988-2013 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
#include <regex.h>
#include "error.h"
+#include "filenamecat.h"
#include "quote.h"
#include "quotearg.h"
#include "safe-read.h"
#include "stdlib--.h"
#include "xfreopen.h"
-/* The official name of this program (e.g., no `g' prefix). */
+/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "tac"
#define AUTHORS \
/* True if we have ever read standard input. */
static bool have_read_stdin = false;
-/* If true, print `separator' along with the record preceding it
+/* If true, print 'separator' along with the record preceding it
in the file; otherwise with the record following it. */
static bool separator_ends_record;
-/* 0 if `separator' is to be matched as a regular expression;
- otherwise, the length of `separator', used as a sentinel to
+/* 0 if 'separator' is to be matched as a regular expression;
+ otherwise, the length of 'separator', used as a sentinel to
stop the search. */
static size_t sentinel_length;
-/* The length of a match with `separator'. If `sentinel_length' is 0,
- `match_length' is computed every time a match succeeds;
- otherwise, it is simply the length of `separator'. */
+/* The length of a match with 'separator'. If 'sentinel_length' is 0,
+ 'match_length' is computed every time a match succeeds;
+ otherwise, it is simply the length of 'separator'. */
static size_t match_length;
/* The input buffer. */
static char *G_buffer;
-/* The number of bytes to read at once into `buffer'. */
+/* The number of bytes to read at once into 'buffer'. */
static size_t read_size;
-/* The size of `buffer'. This is read_size * 2 + sentinel_length + 2.
- The extra 2 bytes allow `past_end' to have a value beyond the
- end of `G_buffer' and `match_start' to run off the front of `G_buffer'. */
+/* The size of 'buffer'. This is read_size * 2 + sentinel_length + 2.
+ The extra 2 bytes allow 'past_end' to have a value beyond the
+ end of 'G_buffer' and 'match_start' to run off the front of 'G_buffer'. */
static size_t G_buffer_size;
-/* The compiled regular expression representing `separator'. */
+/* The compiled regular expression representing 'separator'. */
static struct re_pattern_buffer compiled_separator;
static char compiled_separator_fastmap[UCHAR_MAX + 1];
static struct re_registers regs;
usage (int status)
{
if (status != EXIT_SUCCESS)
- fprintf (stderr, _("Try `%s --help' for more information.\n"),
- program_name);
+ emit_try_help ();
else
{
printf (_("\
static bool
tac_seekable (int input_fd, const char *file)
{
- /* Pointer to the location in `G_buffer' where the search for
+ /* Pointer to the location in 'G_buffer' where the search for
the next separator will begin. */
char *match_start;
- /* Pointer to one past the rightmost character in `G_buffer' that
+ /* Pointer to one past the rightmost character in 'G_buffer' that
has not been printed yet. */
char *past_end;
- /* Length of the record growing in `G_buffer'. */
+ /* Length of the record growing in 'G_buffer'. */
size_t saved_record_size;
/* Offset in the file of the next read. */
off_t file_pos;
- /* True if `output' has not been called yet for any file.
+ /* True if 'output' has not been called yet for any file.
Only used when the separator is attached to the preceding record. */
bool first_time = true;
char first_char = *separator; /* Speed optimization, non-regexp. */
return true; /* It's an empty file. */
/* Arrange for the first read to lop off enough to leave the rest of the
- file a multiple of `read_size'. Since `read_size' can change, this may
+ file a multiple of 'read_size'. Since 'read_size' can change, this may
not always hold during the program run, but since it usually will, leave
it here for i/o efficiency (page/sector boundaries and all that).
Note: the efficiency gain has not been verified. */
if (saved_record_size == 0)
saved_record_size = read_size;
file_pos -= saved_record_size;
- /* `file_pos' now points to the start of the last (probably partial) block
+ /* 'file_pos' now points to the start of the last (probably partial) block
in the input file. */
if (lseek (input_fd, file_pos, SEEK_SET) < 0)
while (true)
{
- /* Search backward from `match_start' - 1 to `G_buffer' for a match
- with `separator'; for speed, use strncmp if `separator' contains no
+ /* Search backward from 'match_start' - 1 to 'G_buffer' for a match
+ with 'separator'; for speed, use strncmp if 'separator' contains no
metacharacters.
- If the match succeeds, set `match_start' to point to the start of
- the match and `match_length' to the length of the match.
- Otherwise, make `match_start' < `G_buffer'. */
+ If the match succeeds, set 'match_start' to point to the start of
+ the match and 'match_length' to the length of the match.
+ Otherwise, make 'match_start' < 'G_buffer'. */
if (sentinel_length == 0)
{
size_t i = match_start - G_buffer;
}
else
{
- /* `match_length' is constant for non-regexp boundaries. */
+ /* 'match_length' is constant for non-regexp boundaries. */
while (*--match_start != first_char
|| (match_length1 && strncmp (match_start + 1, separator1,
match_length1)))
/* Do nothing. */ ;
}
- /* Check whether we backed off the front of `G_buffer' without finding
- a match for `separator'. */
+ /* Check whether we backed off the front of 'G_buffer' without finding
+ a match for 'separator'. */
if (match_start < G_buffer)
{
if (file_pos == 0)
saved_record_size = past_end - G_buffer;
if (saved_record_size > read_size)
{
- /* `G_buffer_size' is about twice `read_size', so since
- we want to read in another `read_size' bytes before
- the data already in `G_buffer', we need to increase
- `G_buffer_size'. */
+ /* 'G_buffer_size' is about twice 'read_size', so since
+ we want to read in another 'read_size' bytes before
+ the data already in 'G_buffer', we need to increase
+ 'G_buffer_size'. */
char *newbuffer;
size_t offset = sentinel_length ? sentinel_length : 1;
ptrdiff_t match_start_offset = match_start - G_buffer;
The source and destination regions probably overlap. */
memmove (G_buffer + read_size, G_buffer, saved_record_size);
past_end = G_buffer + read_size + saved_record_size;
- /* For non-regexp searches, avoid unneccessary scanning. */
+ /* For non-regexp searches, avoid unnecessary scanning. */
if (sentinel_length)
match_start = G_buffer + read_size;
else
}
else
{
- /* Found a match of `separator'. */
+ /* Found a match of 'separator'. */
if (separator_ends_record)
{
char *match_end = match_start + match_length;
- /* If this match of `separator' isn't at the end of the
+ /* If this match of 'separator' isn't at the end of the
file, print the record. */
if (!first_time || match_end != past_end)
output (match_end, past_end);
#endif
-/* Copy from file descriptor INPUT_FD (corresponding to the named FILE) to
- a temporary file, and set *G_TMP and *G_TEMPFILE to the resulting stream
- and file name. Return true if successful. */
-
+/* A wrapper around mkstemp that gives us both an open stream pointer,
+ FP, and the corresponding FILE_NAME. Always return the same FP/name
+ pair, rewinding/truncating it upon each reuse. */
static bool
-copy_to_temp (FILE **g_tmp, char **g_tempfile, int input_fd, char const *file)
+temp_stream (FILE **fp, char **file_name)
{
- static char *template = NULL;
- static char const *tempdir;
- char *tempfile;
- FILE *tmp;
- int fd;
-
- if (template == NULL)
+ static char *tempfile = NULL;
+ static FILE *tmp_fp;
+ if (tempfile == NULL)
{
- char const * const Template = "%s/tacXXXXXX";
- tempdir = getenv ("TMPDIR");
+ char const *t = getenv ("TMPDIR");
+ char const *tempdir = t ? t : DEFAULT_TMPDIR;
+ tempfile = mfile_name_concat (tempdir, "tacXXXXXX", NULL);
if (tempdir == NULL)
- tempdir = DEFAULT_TMPDIR;
+ {
+ error (0, 0, _("memory exhausted"));
+ return false;
+ }
- /* Subtract 2 for `%s' and add 1 for the trailing NUL byte. */
- template = xmalloc (strlen (tempdir) + strlen (Template) - 2 + 1);
- sprintf (template, Template, tempdir);
- }
+ /* FIXME: there's a small window between a successful mkstemp call
+ and the unlink that's performed by record_or_unlink_tempfile.
+ If we're interrupted in that interval, this code fails to remove
+ the temporary file. On systems that define DONT_UNLINK_WHILE_OPEN,
+ the window is much larger -- it extends to the atexit-called
+ unlink_tempfile.
+ FIXME: clean up upon fatal signal. Don't block them, in case
+ $TMPFILE is a remote file system. */
- /* FIXME: there's a small window between a successful mkstemp call
- and the unlink that's performed by record_or_unlink_tempfile.
- If we're interrupted in that interval, this code fails to remove
- the temporary file. On systems that define DONT_UNLINK_WHILE_OPEN,
- the window is much larger -- it extends to the atexit-called
- unlink_tempfile.
- FIXME: clean up upon fatal signal. Don't block them, in case
- $TMPFILE is a remote file system. */
-
- tempfile = template;
- fd = mkstemp (template);
- if (fd < 0)
- {
- error (0, errno, _("cannot create temporary file in %s"),
- quote (tempdir));
- return false;
- }
+ int fd = mkstemp (tempfile);
+ if (fd < 0)
+ {
+ error (0, errno, _("failed to create temporary file in %s"),
+ quote (tempdir));
+ goto Reset;
+ }
- tmp = fdopen (fd, (O_BINARY ? "w+b" : "w+"));
- if (! tmp)
+ tmp_fp = fdopen (fd, (O_BINARY ? "w+b" : "w+"));
+ if (! tmp_fp)
+ {
+ error (0, errno, _("failed to open %s for writing"),
+ quote (tempfile));
+ close (fd);
+ unlink (tempfile);
+ Reset:
+ free (tempfile);
+ tempfile = NULL;
+ return false;
+ }
+
+ record_or_unlink_tempfile (tempfile, tmp_fp);
+ }
+ else
{
- error (0, errno, _("cannot open %s for writing"), quote (tempfile));
- close (fd);
- unlink (tempfile);
- return false;
+ if (fseeko (tmp_fp, 0, SEEK_SET) < 0
+ || ftruncate (fileno (tmp_fp), 0) < 0)
+ {
+ error (0, errno, _("failed to rewind stream for %s"),
+ quote (tempfile));
+ return false;
+ }
}
- record_or_unlink_tempfile (tempfile, tmp);
+ *fp = tmp_fp;
+ *file_name = tempfile;
+ return true;
+}
+
+/* Copy from file descriptor INPUT_FD (corresponding to the named FILE) to
+ a temporary file, and set *G_TMP and *G_TEMPFILE to the resulting stream
+ and file name. Return true if successful. */
+
+static bool
+copy_to_temp (FILE **g_tmp, char **g_tempfile, int input_fd, char const *file)
+{
+ FILE *fp;
+ char *file_name;
+ if (!temp_stream (&fp, &file_name))
+ return false;
while (1)
{
goto Fail;
}
- if (fwrite (G_buffer, 1, bytes_read, tmp) != bytes_read)
+ if (fwrite (G_buffer, 1, bytes_read, fp) != bytes_read)
{
- error (0, errno, _("%s: write error"), quotearg_colon (tempfile));
+ error (0, errno, _("%s: write error"), quotearg_colon (file_name));
goto Fail;
}
}
- if (fflush (tmp) != 0)
+ if (fflush (fp) != 0)
{
- error (0, errno, _("%s: write error"), quotearg_colon (tempfile));
+ error (0, errno, _("%s: write error"), quotearg_colon (file_name));
goto Fail;
}
- *g_tmp = tmp;
- *g_tempfile = tempfile;
+ *g_tmp = fp;
+ *g_tempfile = file_name;
return true;
Fail:
- fclose (tmp);
+ fclose (fp);
return false;
}
{
FILE *tmp_stream;
char *tmp_file;
- return (copy_to_temp (&tmp_stream, &tmp_file, input_fd, file)
- && tac_seekable (fileno (tmp_stream), tmp_file));
+ if (!copy_to_temp (&tmp_stream, &tmp_file, input_fd, file))
+ return false;
+
+ bool ok = tac_seekable (fileno (tmp_stream), tmp_file);
+ return ok;
}
/* Print FILE in reverse, copying it to a temporary
fd = open (filename, O_RDONLY | O_BINARY);
if (fd < 0)
{
- error (0, errno, _("cannot open %s for reading"), quote (filename));
+ error (0, errno, _("failed to open %s for reading"),
+ quote (filename));
return false;
}
}
G_buffer = xmalloc (G_buffer_size);
if (sentinel_length)
{
- strcpy (G_buffer, separator);
+ memcpy (G_buffer, separator, sentinel_length + 1);
G_buffer += sentinel_length;
}
else