/* tac - concatenate and print files in reverse
- Copyright (C) 88, 89, 90, 91, 95, 1996 Free Software Foundation, Inc.
+ Copyright (C) 88,89,90,91,95,96,97, 1998 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 <stdio.h>
#include <getopt.h>
#include <sys/types.h>
-#include <signal.h>
+#include "system.h"
+
#if WITH_REGEX
# include <regex.h>
#else
# include <rx.h>
#endif
-#include "system.h"
-#include "error.h"
-#ifndef STDC_HEADERS
-char *malloc ();
-char *realloc ();
-#endif
+#include "error.h"
+#include "safe-read.h"
#ifndef DEFAULT_TMPDIR
-#define DEFAULT_TMPDIR "/tmp"
+# define DEFAULT_TMPDIR "/tmp"
#endif
/* The number of bytes per atomic read. */
char *mktemp ();
-int full_write ();
-int safe_read ();
-
/* The name this program was run with. */
char *program_name;
static int match_length;
/* The input buffer. */
-static char *buffer;
+static char *G_buffer;
/* The number of bytes to read at once into `buffer'. */
static unsigned 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 `buffer' and `match_start' to run off the front of `buffer'. */
-static unsigned buffer_size;
+ end of `G_buffer' and `match_start' to run off the front of `G_buffer'. */
+static unsigned G_buffer_size;
/* The compiled regular expression representing `separator'. */
static struct re_pattern_buffer compiled_separator;
-/* The name of a temporary file containing a copy of pipe input. */
-static char *tempfile;
-
/* If nonzero, display usage information and exit. */
static int show_help;
--help display this help and exit\n\
--version output version information and exit\n\
"));
- puts (_("\nReport bugs to textutils-bugs@gnu.ai.mit.edu"));
+ puts (_("\nReport bugs to <textutils-bugs@gnu.org>."));
}
exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
-static void
-cleanup (void)
-{
- unlink (tempfile);
-}
-
-static void
-cleanup_fatal (void)
-{
- cleanup ();
- exit (EXIT_FAILURE);
-}
-
-static RETSIGTYPE
-sighandler (int sig)
-{
-#ifdef SA_INTERRUPT
- struct sigaction sigact;
-
- sigact.sa_handler = SIG_DFL;
- sigemptyset (&sigact.sa_mask);
- sigact.sa_flags = 0;
- sigaction (sig, &sigact, NULL);
-#else /* !SA_INTERRUPT */
- signal (sig, SIG_DFL);
-#endif /* SA_INTERRUPT */
- cleanup ();
- kill (getpid (), sig);
-}
-
-/* Allocate N bytes of memory dynamically, with error checking. */
-
-static char *
-xmalloc (unsigned int n)
-{
- char *p;
-
- p = malloc (n);
- if (p == 0)
- {
- error (0, 0, _("virtual memory exhausted"));
- cleanup_fatal ();
- }
- return p;
-}
-
-/* Change the size of memory area P to N bytes, with error checking. */
-
-static char *
-xrealloc (char *p, unsigned int n)
-{
- p = realloc (p, n);
- if (p == 0)
- {
- error (0, 0, _("virtual memory exhausted"));
- cleanup_fatal ();
- }
- return p;
-}
-
-static void
-xwrite (int desc, const char *buffer, int size)
-{
- if (full_write (desc, buffer, size) < 0)
- {
- error (0, errno, _("write error"));
- cleanup_fatal ();
- }
-}
-
/* Print the characters from START to PAST_END - 1.
If START is NULL, just flush the buffer. */
if (start == 0)
{
- xwrite (STDOUT_FILENO, buffer, bytes_in_buffer);
+ fwrite (buffer, 1, bytes_in_buffer, stdout);
bytes_in_buffer = 0;
return;
}
memcpy (buffer + bytes_in_buffer, start, bytes_available);
bytes_to_add -= bytes_available;
start += bytes_available;
- xwrite (STDOUT_FILENO, buffer, WRITESIZE);
+ fwrite (buffer, 1, WRITESIZE, stdout);
bytes_in_buffer = 0;
bytes_available = WRITESIZE;
}
Return 0 if ok, 1 if an error occurs. */
static int
-tac (int fd, const char *file)
+tac_stream (FILE *in, const char *file)
{
- /* Pointer to the location in `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 `buffer' that
+
+ /* Pointer to one past the rightmost character in `G_buffer' that
has not been printed yet. */
char *past_end;
- unsigned saved_record_size; /* Length of the record growing in `buffer'. */
- off_t file_pos; /* Offset in the file of the next read. */
+
+ /* Length of the record growing in `G_buffer'. */
+ unsigned saved_record_size;
+
+ /* Offset in the file of the next read. */
+ off_t file_pos;
+
/* Nonzero if `output' has not been called yet for any file.
Only used when the separator is attached to the preceding record. */
int first_time = 1;
struct re_registers regs;
/* Find the size of the input file. */
- file_pos = lseek (fd, (off_t) 0, SEEK_END);
+ file_pos = lseek (fileno (in), (off_t) 0, SEEK_END);
if (file_pos < 1)
return 0; /* It's an empty file. */
/* `file_pos' now points to the start of the last (probably partial) block
in the input file. */
- lseek (fd, file_pos, SEEK_SET);
- if (safe_read (fd, buffer, saved_record_size) != saved_record_size)
+ lseek (fileno (in), file_pos, SEEK_SET);
+ if (safe_read (fileno (in), G_buffer, saved_record_size) != saved_record_size)
{
error (0, errno, "%s", file);
return 1;
}
- match_start = past_end = buffer + saved_record_size;
+ match_start = past_end = G_buffer + saved_record_size;
/* For non-regexp search, move past impossible positions for a match. */
if (sentinel_length)
match_start -= match_length1;
for (;;)
{
- /* Search backward from `match_start' - 1 to `buffer' for a match
+ /* 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' < `buffer'. */
+ Otherwise, make `match_start' < `G_buffer'. */
if (sentinel_length == 0)
{
- int i = match_start - buffer;
+ int i = match_start - G_buffer;
int ret;
- ret = re_search (&compiled_separator, buffer, i, i - 1, -i, ®s);
+ ret = re_search (&compiled_separator, G_buffer, i, i - 1, -i, ®s);
if (ret == -1)
- match_start = buffer - 1;
+ match_start = G_buffer - 1;
else if (ret == -2)
{
- error (0, 0, _("error in regular expression search"));
- cleanup_fatal ();
+ error (EXIT_FAILURE, 0,
+ _("error in regular expression search"));
}
else
{
- match_start = buffer + regs.start[0];
+ match_start = G_buffer + regs.start[0];
match_length = regs.end[0] - regs.start[0];
}
}
/* Do nothing. */ ;
}
- /* Check whether we backed off the front of `buffer' without finding
+ /* Check whether we backed off the front of `G_buffer' without finding
a match for `separator'. */
- if (match_start < buffer)
+ if (match_start < G_buffer)
{
if (file_pos == 0)
{
/* Hit the beginning of the file; print the remaining record. */
- output (buffer, past_end);
+ output (G_buffer, past_end);
return 0;
}
- saved_record_size = past_end - buffer;
+ saved_record_size = past_end - G_buffer;
if (saved_record_size > read_size)
{
- /* `buffer_size' is about twice `read_size', so since
+ /* `G_buffer_size' is about twice `read_size', so since
we want to read in another `read_size' bytes before
- the data already in `buffer', we need to increase
- `buffer_size'. */
+ the data already in `G_buffer', we need to increase
+ `G_buffer_size'. */
char *newbuffer;
int offset = sentinel_length ? sentinel_length : 1;
read_size *= 2;
- buffer_size = read_size * 2 + sentinel_length + 2;
- newbuffer = xrealloc (buffer - offset, buffer_size) + offset;
+ G_buffer_size = read_size * 2 + sentinel_length + 2;
+ newbuffer = xrealloc (G_buffer - offset, G_buffer_size);
+ newbuffer += offset;
/* Adjust the pointers for the new buffer location. */
- match_start += newbuffer - buffer;
- past_end += newbuffer - buffer;
- buffer = newbuffer;
+ match_start += newbuffer - G_buffer;
+ past_end += newbuffer - G_buffer;
+ G_buffer = newbuffer;
}
/* Back up to the start of the next bufferfull of the file. */
read_size = file_pos;
file_pos = 0;
}
- lseek (fd, file_pos, SEEK_SET);
+ lseek (fileno (in), file_pos, SEEK_SET);
/* Shift the pending record data right to make room for the new.
The source and destination regions probably overlap. */
- memmove (buffer + read_size, buffer, saved_record_size);
- past_end = buffer + read_size + saved_record_size;
+ 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. */
if (sentinel_length)
- match_start = buffer + read_size;
+ match_start = G_buffer + read_size;
else
match_start = past_end;
- if (safe_read (fd, buffer, read_size) != read_size)
+ if (safe_read (fileno (in), G_buffer, read_size) != read_size)
{
error (0, errno, "%s", file);
return 1;
static int
tac_file (const char *file)
{
- int fd, errors;
+ int errors;
+ FILE *in;
- fd = open (file, O_RDONLY);
- if (fd == -1)
+ in = fopen (file, "r");
+ if (in == NULL)
{
error (0, errno, "%s", file);
return 1;
}
- errors = tac (fd, file);
- if (close (fd) < 0)
+ errors = tac_stream (in, file);
+ if (ferror (in) || fclose (in) == EOF)
{
error (0, errno, "%s", file);
return 1;
return errors;
}
-/* Make a copy of the standard input in `tempfile'. */
+/* Make a copy of the standard input in `FIXME'. */
static void
-save_stdin (void)
+save_stdin (FILE **g_tmp, char **g_tempfile)
{
static char *template = NULL;
static char *tempdir;
- int fd;
+ static char *tempfile;
+ FILE *tmp;
int bytes_read;
+ int fd;
if (template == NULL)
{
sprintf (template, "%s/tacXXXXXX", tempdir);
tempfile = mktemp (template);
- fd = creat (tempfile, 0600);
- if (fd == -1)
- {
- error (0, errno, "%s", tempfile);
- cleanup_fatal ();
- }
- while ((bytes_read = safe_read (0, buffer, read_size)) > 0)
- if (full_write (fd, buffer, bytes_read) < 0)
- {
- error (0, errno, "%s", tempfile);
- cleanup_fatal ();
- }
- if (close (fd) < 0)
- {
- error (0, errno, "%s", tempfile);
- cleanup_fatal ();
- }
+ fd = open (tempfile, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
+ if (fd == -1 || (tmp = fdopen (fd, "rw")) == NULL)
+ error (EXIT_FAILURE, errno, "%s", tempfile);
+ tmp = fdopen (fd, "rw");
+ if (tmp == NULL)
+ error (EXIT_FAILURE, errno, "%s", tempfile);
+ unlink (tempfile);
+
+ while ((bytes_read = safe_read (0, G_buffer, read_size)) > 0)
+ fwrite (G_buffer, 1, bytes_read, tmp);
+
+ if (ferror (tmp) || fflush (tmp) == EOF)
+ error (EXIT_FAILURE, errno, "%s", tempfile);
+
+ if (fseek (tmp, (long int) 0, SEEK_SET))
+ error (EXIT_FAILURE, errno, "%s", tempfile);
+
if (bytes_read == -1)
- {
- error (0, errno, _("read error"));
- cleanup_fatal ();
- }
+ error (EXIT_FAILURE, errno, _("read error"));
+
+ *g_tmp = tmp;
+ *g_tempfile = tempfile;
}
/* Print the standard input in reverse, saving it to temporary
- file `tempfile' first if it is a pipe.
+ file first if it is a pipe.
Return 0 if ok, 1 if an error occurs. */
static int
tac_stdin (void)
{
- /* Previous values of signal handlers. */
- RETSIGTYPE (*sigint) (), (*sighup) (), (*sigpipe) (), (*sigterm) ();
int errors;
struct stat stats;
-#ifdef SA_INTERRUPT
- struct sigaction oldact, newact;
-#endif /* SA_INTERRUPT */
/* No tempfile is needed for "tac < file".
Use fstat instead of checking for errno == ESPIPE because
error (0, errno, _("standard input"));
return 1;
}
+
if (S_ISREG (stats.st_mode))
- return tac (0, _("standard input"));
-
-#ifdef SA_INTERRUPT
- newact.sa_handler = sighandler;
- sigemptyset (&newact.sa_mask);
- newact.sa_flags = 0;
-
- sigaction (SIGINT, NULL, &oldact);
- sigint = oldact.sa_handler;
- if (sigint != SIG_IGN)
- sigaction (SIGINT, &newact, NULL);
-
- sigaction (SIGHUP, NULL, &oldact);
- sighup = oldact.sa_handler;
- if (sighup != SIG_IGN)
- sigaction (SIGHUP, &newact, NULL);
-
- sigaction (SIGPIPE, NULL, &oldact);
- sigpipe = oldact.sa_handler;
- if (sigpipe != SIG_IGN)
- sigaction (SIGPIPE, &newact, NULL);
-
- sigaction (SIGTERM, NULL, &oldact);
- sigterm = oldact.sa_handler;
- if (sigterm != SIG_IGN)
- sigaction (SIGTERM, &newact, NULL);
-#else /* !SA_INTERRUPT */
- sigint = signal (SIGINT, SIG_IGN);
- if (sigint != SIG_IGN)
- signal (SIGINT, sighandler);
-
- sighup = signal (SIGHUP, SIG_IGN);
- if (sighup != SIG_IGN)
- signal (SIGHUP, sighandler);
-
- sigpipe = signal (SIGPIPE, SIG_IGN);
- if (sigpipe != SIG_IGN)
- signal (SIGPIPE, sighandler);
-
- sigterm = signal (SIGTERM, SIG_IGN);
- if (sigterm != SIG_IGN)
- signal (SIGTERM, sighandler);
-#endif /* SA_INTERRUPT */
-
- save_stdin ();
-
- errors = tac_file (tempfile);
+ {
+ errors = tac_stream (stdin, _("standard input"));
+ }
+ else
+ {
+ FILE *tmp_stream;
+ char *tmp_file;
+ save_stdin (&tmp_stream, &tmp_file);
+ errors = tac_stream (tmp_stream, tmp_file);
+ }
- unlink (tempfile);
+ return errors;
+}
-#ifdef SA_INTERRUPT
- newact.sa_handler = sigint;
- sigaction (SIGINT, &newact, NULL);
- newact.sa_handler = sighup;
- sigaction (SIGHUP, &newact, NULL);
- newact.sa_handler = sigterm;
- sigaction (SIGTERM, &newact, NULL);
- newact.sa_handler = sigpipe;
- sigaction (SIGPIPE, &newact, NULL);
-#else /* !SA_INTERRUPT */
- signal (SIGINT, sigint);
- signal (SIGHUP, sighup);
- signal (SIGTERM, sigterm);
- signal (SIGPIPE, sigpipe);
-#endif /* SA_INTERRUPT */
+/* BUF_END_PLUS_ONE points one byte past the end of the buffer
+ to be searched. */
- return errors;
+static void *
+memrchr (const char *buf_start, const char *buf_end_plus_one, int c)
+{
+ const char *p = buf_end_plus_one;
+ while (buf_start <= --p)
+ {
+ if (*(const unsigned char *) p == c)
+ return (void *) p;
+ }
+ return NULL;
+}
+
+/* FIXME: describe */
+
+static int
+tac_mem (const char *buf, size_t n_bytes, FILE *out)
+{
+ const char *nl;
+ const char *bol;
+
+ if (n_bytes == 0)
+ return 0;
+
+ nl = memrchr (buf, buf + n_bytes, '\n');
+ bol = (nl == NULL ? buf : nl + 1);
+
+ /* If the last line of the input file has no terminating newline,
+ treat it as a special case. */
+ if (bol < buf + n_bytes)
+ {
+ /* Print out the line from bol to end of input. */
+ fwrite (bol, 1, (buf + n_bytes) - bol, out);
+
+ /* Add a newline here. Otherwise, the first and second lines
+ of output would appear to have been joined. */
+ fputc ('\n', out);
+ }
+
+ while ((nl = memrchr (buf, bol - 1, '\n')) != NULL)
+ {
+ /* Output the line (which includes a trailing newline)
+ from NL+1 to BOL-1. */
+ fwrite (nl + 1, 1, bol - (nl + 1), out);
+
+ bol = nl + 1;
+ }
+
+ /* If there's anything left, output the last line: BUF .. BOL-1.
+ When the first byte of the input is a newline, there is nothing
+ left to do here. */
+ if (buf < bol)
+ fwrite (buf, 1, bol - buf, out);
+
+ /* FIXME: this is work in progress.... */
+ return ferror (out);
+}
+
+/* FIXME: describe */
+
+static int
+tac_stdin_to_mem (void)
+{
+ char *buf = NULL;
+ size_t bufsiz = 8 * BUFSIZ;
+ size_t delta = 8 * BUFSIZ;
+ size_t n_bytes = 0;
+
+ while (1)
+ {
+ int bytes_read;
+ if (buf == NULL)
+ buf = (char *) malloc (bufsiz);
+ else
+ buf = (char *) realloc (buf, bufsiz);
+
+ if (buf == NULL)
+ {
+ /* Free the buffer and fall back on the code that relies on a
+ temporary file. */
+ free (buf);
+ /* FIXME */
+ abort ();
+ }
+ bytes_read = safe_read (STDIN_FILENO, buf + n_bytes, bufsiz - n_bytes);
+ if (bytes_read == 0)
+ break;
+ n_bytes += bytes_read;
+ if (bytes_read < 0)
+ error (1, errno, _("read error"));
+
+ bufsiz += delta;
+ }
+
+ tac_mem (buf, n_bytes, stdout);
+
+ return 0;
}
int
sentinel_length = 1;
separator_ends_record = 1;
- while ((optc = getopt_long (argc, argv, "brs:", longopts, (int *) 0))
- != EOF)
+ while ((optc = getopt_long (argc, argv, "brs:", longopts, NULL)) != -1)
{
switch (optc)
{
/* A precaution that will probably never be needed. */
while (sentinel_length * 2 >= read_size)
read_size *= 2;
- buffer_size = read_size * 2 + sentinel_length + 2;
- buffer = xmalloc (buffer_size);
+ G_buffer_size = read_size * 2 + sentinel_length + 2;
+ G_buffer = xmalloc (G_buffer_size);
if (sentinel_length)
{
- strcpy (buffer, separator);
- buffer += sentinel_length;
+ strcpy (G_buffer, separator);
+ G_buffer += sentinel_length;
}
else
- ++buffer;
+ ++G_buffer;
if (optind == argc)
{
have_read_stdin = 1;
- errors = tac_stdin ();
+ errors = tac_stdin_to_mem ();
}
else
for (; optind < argc; ++optind)
if (strcmp (argv[optind], "-") == 0)
{
have_read_stdin = 1;
- errors |= tac_stdin ();
+ errors |= tac_stdin_to_mem ();
}
else
errors |= tac_file (argv[optind]);
if (have_read_stdin && close (0) < 0)
error (EXIT_FAILURE, errno, "-");
- if (close (1) < 0)
+ if (ferror (stdout) || fclose (stdout) == EOF)
error (EXIT_FAILURE, errno, _("write error"));
exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}