/* tac - concatenate and print files in reverse This is the tac utility
Copyright (C) 1988-2018 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */ The GNUv3 license
/* Written by Jay Lepreau (lepreau@cs.utah.edu).
GNU enhancements by David MacKenzie (djm@gnu.ai.mit.edu). */
/* Copy each FILE, or the standard input if none are given or when a
FILE name of "-" is encountered, to the standard output with the
order of the records reversed. The records are separated by
instances of a string, or a newline if none is given. By default, the
separator string is attached to the end of the record that it
follows in the file.
Options:
-b, --before The separator is attached to the beginning
of the record that it precedes in the file.
-r, --regex The separator is a regular expression.
-s, --separator=separator Use SEPARATOR as the record separator.
To reverse a file byte by byte, use (in bash, ksh, or sh):
tac -r -s '.\|
' file */
#include <config.h> Provides system specific information
#include <stdio.h> Provides standard I/O capability
#include <getopt.h> ...!includes auto-comment...
#include <sys/types.h> Provides system data types
#include "system.h" ...!includes auto-comment...
#include <regex.h> ...!includes auto-comment...
#include "die.h" ...!includes auto-comment...
#include "error.h" ...!includes auto-comment...
#include "filenamecat.h" ...!includes auto-comment...
#include "safe-read.h" ...!includes auto-comment...
#include "stdlib--.h" ...!includes auto-comment...
#include "xbinary-io.h" ...!includes auto-comment...
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "tac" Line 54
#define AUTHORS \ Line 56
proper_name ("Jay Lepreau"), \ Line 57
proper_name ("David MacKenzie") Line 58
#if defined __MSDOS__ || defined _WIN32 Line 60
/* Define this to non-zero on systems for which the regular mechanism
(of unlinking an open file and expecting to be able to write, seek
back to the beginning, then reread it) doesn't work. E.g., on Windows
and DOS systems. */
# define DONT_UNLINK_WHILE_OPEN 1 Line 65
#endif Line 66
#ifndef DEFAULT_TMPDIR Line 69
# define DEFAULT_TMPDIR "/tmp" Line 70
#endif Line 71
/* The number of bytes per atomic read. */
#define INITIAL_READSIZE 8192 Line 74
/* The number of bytes per atomic write. */
#define WRITESIZE 8192 Line 77
/* The string that separates the records of the file. */
static char const *separator; Line 80
/* True if we have ever read standard input. */
static bool have_read_stdin = false; Line 83
/* If true, print 'separator' along with the record preceding it
in the file; otherwise with the record following it. */
static bool separator_ends_record; Line 87
/* 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; Line 92
/* 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; Line 97
/* The input buffer. */
static char *G_buffer; Line 100
/* The number of bytes to read at once into 'buffer'. */
static size_t read_size; Line 103
/* 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; Line 108
/* The compiled regular expression representing 'separator'. */
static struct re_pattern_buffer compiled_separator; Line 111
static char compiled_separator_fastmap[UCHAR_MAX + 1]; Line 112
static struct re_registers regs; Line 113
static struct option const longopts[] = Line 115
{
{"before", no_argument, NULL, 'b'}, Line 117
{"regex", no_argument, NULL, 'r'}, Line 118
{"separator", required_argument, NULL, 's'}, Line 119
{GETOPT_HELP_OPTION_DECL}, Line 120
{GETOPT_VERSION_OPTION_DECL}, Line 121
{NULL, 0, NULL, 0} Line 122
}; Block 1
void Line 125
usage (int status) Line 126
{
if (status != EXIT_SUCCESS) Line 128
emit_try_help (); ...!common auto-comment...
else Line 130
{
printf (_("\ Line 132
Usage: %s [OPTION]... [FILE]...\n\ Line 133
"), Line 134
program_name); Line 135
fputs (_("\ Line 136
Write each FILE to standard output, last line first.\n\ Line 137
"), stdout); Line 138
emit_stdin_note (); ...!common auto-comment...
emit_mandatory_arg_note (); ...!common auto-comment...
fputs (_("\ Line 143
-b, --before attach the separator before instead of after\n\ Line 144
-r, --regex interpret the separator as a regular expression\n\ Line 145
-s, --separator=STRING use STRING as the separator instead of newline\n\ Line 146
"), stdout); Line 147
fputs (HELP_OPTION_DESCRIPTION, stdout); Line 148
fputs (VERSION_OPTION_DESCRIPTION, stdout); Line 149
emit_ancillary_info (PROGRAM_NAME); Line 150
}
exit (status); Line 152
} Block 2
/* Print the characters from START to PAST_END - 1.
If START is NULL, just flush the buffer. */
static void Line 158
output (const char *start, const char *past_end) Line 159
{
static char buffer[WRITESIZE]; Line 161
static size_t bytes_in_buffer = 0; Line 162
size_t bytes_to_add = past_end - start; Line 163
size_t bytes_available = WRITESIZE - bytes_in_buffer; Line 164
if (start == 0) Line 166
{
fwrite (buffer, 1, bytes_in_buffer, stdout); Line 168...!syscalls auto-comment...
bytes_in_buffer = 0; Line 169
return; Line 170
}
/* Write out as many full buffers as possible. */
while (bytes_to_add >= bytes_available) Line 174
{
memcpy (buffer + bytes_in_buffer, start, bytes_available); Line 176
bytes_to_add -= bytes_available; Line 177
start += bytes_available; Line 178
fwrite (buffer, 1, WRITESIZE, stdout); Line 179...!syscalls auto-comment...
bytes_in_buffer = 0; Line 180
bytes_available = WRITESIZE; Line 181
}
memcpy (buffer + bytes_in_buffer, start, bytes_to_add); Line 184
bytes_in_buffer += bytes_to_add; Line 185
} Block 3
/* Print in reverse the file open on descriptor FD for reading FILE.
The file is already positioned at FILE_POS, which should be near its end.
Return true if successful. */
static bool Line 192
tac_seekable (int input_fd, const char *file, off_t file_pos) Line 193
{
/* Pointer to the location in 'G_buffer' where the search for
the next separator will begin. */
char *match_start; Line 197
/* Pointer to one past the rightmost character in 'G_buffer' that
has not been printed yet. */
char *past_end; Line 201
/* Length of the record growing in 'G_buffer'. */
size_t saved_record_size; Line 204
/* 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; Line 208
char first_char = *separator; /* Speed optimization, non-regexp. */ Line 209
char const *separator1 = separator + 1; /* Speed optimization, non-regexp. */ Line 210
size_t match_length1 = match_length - 1; /* Speed optimization, non-regexp. */Line 211
/* 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
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. */
size_t remainder = file_pos % read_size; Line 218
if (remainder != 0) Line 219
{
file_pos -= remainder; Line 221
if (lseek (input_fd, file_pos, SEEK_SET) < 0) Line 222
error (0, errno, _("%s: seek failed"), quotef (file)); Line 223
}
/* Scan backward, looking for end of file. This caters to proc-like
file systems where the file size is just an estimate. */
while ((saved_record_size = safe_read (input_fd, G_buffer, read_size)) == 0 Line 228...!syscalls auto-comment...
&& file_pos != 0) Line 229
{
off_t rsize = read_size; Line 231
if (lseek (input_fd, -rsize, SEEK_CUR) < 0) Line 232
error (0, errno, _("%s: seek failed"), quotef (file)); Line 233
file_pos -= read_size; Line 234
}
/* Now scan forward, looking for end of file. */
while (saved_record_size == read_size) Line 238
{
size_t nread = safe_read (input_fd, G_buffer, read_size); Line 240...!syscalls auto-comment...
if (nread == 0) Line 241
break; Line 242
saved_record_size = nread; Line 243
if (saved_record_size == SAFE_READ_ERROR) Line 244
break; Line 245
file_pos += nread; Line 246
}
if (saved_record_size == SAFE_READ_ERROR) Line 249
{
error (0, errno, _("%s: read error"), quotef (file)); Line 251
return false; Line 252
}
match_start = past_end = G_buffer + saved_record_size; Line 255
/* For non-regexp search, move past impossible positions for a match. */
if (sentinel_length) Line 257
match_start -= match_length1; Line 258
while (true) Line 260
{
/* 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 (sentinel_length == 0) Line 268
{
size_t i = match_start - G_buffer; Line 270
regoff_t ri = i; Line 271
regoff_t range = 1 - ri; Line 272
regoff_t ret; Line 273
if (1 < range) Line 275
die (EXIT_FAILURE, 0, _("record too large")); Line 276
if (range == 1 Line 278
|| ((ret = re_search (&compiled_separator, G_buffer, Line 279
i, i - 1, range, ®s)) Line 280
== -1)) Line 281
match_start = G_buffer - 1; Line 282
else if (ret == -2) Line 283
{
die (EXIT_FAILURE, 0, Line 285
_("error in regular expression search")); Line 286
}
else Line 288
{
match_start = G_buffer + regs.start[0]; Line 290
match_length = regs.end[0] - regs.start[0]; Line 291
}
}
else Line 294
{
/* 'match_length' is constant for non-regexp boundaries. */
while (*--match_start != first_char Line 297
|| (match_length1 && !STREQ_LEN (match_start + 1, separator1, Line 298
match_length1))) Line 299
/* Do nothing. */ ;
}
/* Check whether we backed off the front of 'G_buffer' without finding
a match for 'separator'. */
if (match_start < G_buffer) Line 305
{
if (file_pos == 0) Line 307
{
/* Hit the beginning of the file; print the remaining record. */
output (G_buffer, past_end); Line 310
return true; Line 311
}
saved_record_size = past_end - G_buffer; Line 314
if (saved_record_size > read_size) Line 315
{
/* '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; Line 321
size_t offset = sentinel_length ? sentinel_length : 1; Line 322
size_t old_G_buffer_size = G_buffer_size; Line 323
read_size *= 2; Line 325
G_buffer_size = read_size * 2 + sentinel_length + 2; Line 326
if (G_buffer_size < old_G_buffer_size) Line 327
xalloc_die (); ...!common auto-comment...
newbuffer = xrealloc (G_buffer - offset, G_buffer_size); Line 329
newbuffer += offset; Line 330
G_buffer = newbuffer; Line 331
}
/* Back up to the start of the next bufferfull of the file. */
if (file_pos >= read_size) Line 335
file_pos -= read_size; Line 336
else Line 337
{
read_size = file_pos; Line 339
file_pos = 0; Line 340
}
if (lseek (input_fd, file_pos, SEEK_SET) < 0) Line 342
error (0, errno, _("%s: seek failed"), quotef (file)); Line 343
/* Shift the pending record data right to make room for the new.
The source and destination regions probably overlap. */
memmove (G_buffer + read_size, G_buffer, saved_record_size); Line 347
past_end = G_buffer + read_size + saved_record_size; Line 348
/* For non-regexp searches, avoid unnecessary scanning. */
if (sentinel_length) Line 350
match_start = G_buffer + read_size; Line 351
else Line 352
match_start = past_end; Line 353
if (safe_read (input_fd, G_buffer, read_size) != read_size) Line 355...!syscalls auto-comment...
{
error (0, errno, _("%s: read error"), quotef (file)); Line 357
return false; Line 358
}
}
else Line 361
{
/* Found a match of 'separator'. */
if (separator_ends_record) Line 364
{
char *match_end = match_start + match_length; Line 366
/* If this match of 'separator' isn't at the end of the
file, print the record. */
if (!first_time || match_end != past_end) Line 370
output (match_end, past_end); Line 371
past_end = match_end; Line 372
first_time = false; Line 373
}
else Line 375
{
output (match_start, past_end); Line 377
past_end = match_start; Line 378
}
/* For non-regex matching, we can back up. */
if (sentinel_length > 0) Line 382
match_start -= match_length - 1; Line 383
}
}
}
#if DONT_UNLINK_WHILE_OPEN Line 388
/* FIXME-someday: remove all of this DONT_UNLINK_WHILE_OPEN junk.
Using atexit like this is wrong, since it can fail
when called e.g. 32 or more times.
But this isn't a big deal, since the code is used only on WOE/DOS
systems, and few people invoke tac on that many nonseekable files. */
static const char *file_to_remove; Line 396
static FILE *fp_to_close; Line 397
static void Line 399
unlink_tempfile (void) Line 400
{
fclose (fp_to_close); Line 402...!syscalls auto-comment...
unlink (file_to_remove); Line 403...!syscalls auto-comment......!syscalls auto-comment...
} Block 5
static void Line 406
record_or_unlink_tempfile (char const *fn, FILE *fp) Line 407
{
if (!file_to_remove) Line 409
{
file_to_remove = fn; Line 411
fp_to_close = fp; Line 412
atexit (unlink_tempfile); Close stdout on exit (see gnulib)
}
} Block 6
#else Line 417
static void Line 419
record_or_unlink_tempfile (char const *fn, FILE *fp _GL_UNUSED) Line 420
{
unlink (fn); Line 422...!syscalls auto-comment......!syscalls auto-comment...
} Block 7
#endif Line 425
/* 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 Line 430
temp_stream (FILE **fp, char **file_name) Line 431
{
static char *tempfile = NULL; Line 433
static FILE *tmp_fp; Line 434
if (tempfile == NULL) Line 435
{
char const *t = getenv ("TMPDIR"); Line 437
char const *tempdir = t ? t : DEFAULT_TMPDIR; Line 438
tempfile = mfile_name_concat (tempdir, "tacXXXXXX", NULL); Line 439
if (tempdir == NULL) Line 440
{
error (0, 0, _("memory exhausted")); Line 442
return false; Line 443
}
/* 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. */
int fd = mkstemp (tempfile); Line 455
if (fd < 0) Line 456
{
error (0, errno, _("failed to create temporary file in %s"), Line 458
quoteaf (tempdir)); Line 459
goto Reset; Line 460
}
tmp_fp = fdopen (fd, (O_BINARY ? "w+b" : "w+")); Line 463...!syscalls auto-comment...
if (! tmp_fp) Line 464
{
error (0, errno, _("failed to open %s for writing"), Line 466
quoteaf (tempfile)); Line 467
close (fd); Line 468...!syscalls auto-comment...
unlink (tempfile); Line 469...!syscalls auto-comment......!syscalls auto-comment...
Reset: Line 470
free (tempfile); Line 471
tempfile = NULL; Line 472
return false; Line 473
}
record_or_unlink_tempfile (tempfile, tmp_fp); Line 476
}
else Line 478
{
clearerr (tmp_fp); Line 480
if (fseeko (tmp_fp, 0, SEEK_SET) < 0 Line 481
|| ftruncate (fileno (tmp_fp), 0) < 0) Line 482...!syscalls auto-comment...
{
error (0, errno, _("failed to rewind stream for %s"), Line 484
quoteaf (tempfile)); Line 485
return false; Line 486
}
}
*fp = tmp_fp; Line 490
*file_name = tempfile; Line 491
return true; Line 492
} Block 8
/* 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 the number of bytes copied, or -1 on error. */
static off_t Line 499
copy_to_temp (FILE **g_tmp, char **g_tempfile, int input_fd, char const *file) Line 500
{
FILE *fp; Line 502
char *file_name; Line 503
uintmax_t bytes_copied = 0; Line 504
if (!temp_stream (&fp, &file_name)) Line 505
return -1; Line 506
while (1) Line 508
{
size_t bytes_read = safe_read (input_fd, G_buffer, read_size); Line 510...!syscalls auto-comment...
if (bytes_read == 0) Line 511
break; Line 512
if (bytes_read == SAFE_READ_ERROR) Line 513
{
error (0, errno, _("%s: read error"), quotef (file)); Line 515
return -1; Line 516
}
if (fwrite (G_buffer, 1, bytes_read, fp) != bytes_read) Line 519...!syscalls auto-comment...
{
error (0, errno, _("%s: write error"), quotef (file_name)); Line 521
return -1; Line 522
}
/* Implicitly <= OFF_T_MAX due to preceding fwrite(),
but unsigned type used to avoid compiler warnings
not aware of this fact. */
bytes_copied += bytes_read; Line 528
}
if (fflush (fp) != 0) Line 531
{
error (0, errno, _("%s: write error"), quotef (file_name)); Line 533
return -1; Line 534
}
*g_tmp = fp; Line 537
*g_tempfile = file_name; Line 538
return bytes_copied; Line 539
} Block 9
/* Copy INPUT_FD to a temporary, then tac that file.
Return true if successful. */
static bool Line 545
tac_nonseekable (int input_fd, const char *file) Line 546
{
FILE *tmp_stream; Line 548
char *tmp_file; Line 549
off_t bytes_copied = copy_to_temp (&tmp_stream, &tmp_file, input_fd, file); Line 550
if (bytes_copied < 0) Line 551
return false; Line 552
bool ok = tac_seekable (fileno (tmp_stream), tmp_file, bytes_copied); Line 554
return ok; Line 555
} Block 10
/* Print FILE in reverse, copying it to a temporary
file first if it is not seekable.
Return true if successful. */
static bool Line 562
tac_file (const char *filename) Line 563
{
bool ok; Line 565
off_t file_size; Line 566
int fd; Line 567
bool is_stdin = STREQ (filename, "-"); Line 568
if (is_stdin) Line 570
{
have_read_stdin = true; Line 572
fd = STDIN_FILENO; Line 573
filename = _("standard input"); Line 574
xset_binary_mode (STDIN_FILENO, O_BINARY); Line 575
}
else Line 577
{
fd = open (filename, O_RDONLY | O_BINARY); Line 579...!syscalls auto-comment...
if (fd < 0) Line 580
{
error (0, errno, _("failed to open %s for reading"), Line 582
quoteaf (filename)); Line 583
return false; Line 584
}
}
file_size = lseek (fd, 0, SEEK_END); Line 588
ok = (file_size < 0 || isatty (fd) Line 590
? tac_nonseekable (fd, filename) Line 591
: tac_seekable (fd, filename, file_size)); Line 592
if (!is_stdin && close (fd) != 0) Line 594...!syscalls auto-comment...
{
error (0, errno, _("%s: read error"), quotef (filename)); Line 596
ok = false; Line 597
}
return ok; Line 599
} Block 11
int
main (int argc, char **argv) Line 603
{
const char *error_message; /* Return value from re_compile_pattern. */ Line 605
int optc; Line 606
bool ok; Line 607
size_t half_buffer_size; Line 608
/* Initializer for file_list if no file-arguments
were specified on the command line. */
static char const *const default_file_list[] = {"-", NULL}; Line 612
char const *const *file; Line 613
initialize_main (&argc, &argv); VMS-specific entry point handling wildcard expansion
set_program_name (argv[0]); Retains program name and discards path
setlocale (LC_ALL, ""); Sets up internationalization (i18n)
bindtextdomain (PACKAGE, LOCALEDIR); Assigns i18n directorySets text domain for _() [gettext()] function
textdomain (PACKAGE); Sets text domain for _() [gettext()] function
atexit (close_stdout); Close stdout on exit (see gnulib)
separator = "\n"; Line 623
sentinel_length = 1; Line 624
separator_ends_record = true; Line 625
while ((optc = getopt_long (argc, argv, "brs:", longopts, NULL)) != -1) Line 627
{
switch (optc) Line 629
{
case 'b': Line 631
separator_ends_record = false; Line 632
break; Line 633
case 'r': Line 634
sentinel_length = 0; Line 635
break; Line 636
case 's': Line 637
separator = optarg; Line 638
break; Line 639
case_GETOPT_HELP_CHAR; Line 640
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); Line 641
default: Line 642
usage (EXIT_FAILURE); Line 643
}
}
if (sentinel_length == 0) Line 647
{
if (*separator == 0) Line 649
die (EXIT_FAILURE, 0, _("separator cannot be empty")); Line 650
compiled_separator.buffer = NULL; Line 652
compiled_separator.allocated = 0; Line 653
compiled_separator.fastmap = compiled_separator_fastmap; Line 654
compiled_separator.translate = NULL; Line 655
error_message = re_compile_pattern (separator, strlen (separator), Line 656
&compiled_separator); Line 657
if (error_message) Line 658
die (EXIT_FAILURE, 0, "%s", (error_message)); Line 659
}
else Line 661
match_length = sentinel_length = *separator ? strlen (separator) : 1; Line 662
read_size = INITIAL_READSIZE; Line 664
while (sentinel_length >= read_size / 2) Line 665
{
if (SIZE_MAX / 2 < read_size) Line 667
xalloc_die (); ...!common auto-comment...
read_size *= 2; Line 669
}
half_buffer_size = read_size + sentinel_length + 1; Line 671
G_buffer_size = 2 * half_buffer_size; Line 672
if (! (read_size < half_buffer_size && half_buffer_size < G_buffer_size)) Line 673
xalloc_die (); ...!common auto-comment...
G_buffer = xmalloc (G_buffer_size); Line 675
if (sentinel_length) Line 676
{
memcpy (G_buffer, separator, sentinel_length + 1); Line 678
G_buffer += sentinel_length; Line 679
}
else Line 681
{
++G_buffer; Line 683
}
file = (optind < argc Line 686
? (char const *const *) &argv[optind] Line 687
: default_file_list); Line 688
xset_binary_mode (STDOUT_FILENO, O_BINARY); Line 690
{
ok = true; Line 693
for (size_t i = 0; file[i]; ++i) Line 694
ok &= tac_file (file[i]); Line 695
}
/* Flush the output buffer. */
output ((char *) NULL, (char *) NULL); Line 699
if (have_read_stdin && close (STDIN_FILENO) < 0) Line 701...!syscalls auto-comment...
{
error (0, errno, "-"); Line 703
ok = false; Line 704
}
#ifdef lint Line 707
size_t offset = sentinel_length ? sentinel_length : 1; Line 708
free (G_buffer - offset); Line 709
#endif Line 710
return ok ? EXIT_SUCCESS : EXIT_FAILURE; Line 712
} Block 12