[net.sources] tac.c a program to print lines in reverse order in linear time

trb@masscomp.UUCP (09/21/84)

This program was first posted to net.sources in Oct 1982.  What comes
around goes around, I always say, so here it is, tac.c.

Tac does not read stdin.  Why?  Think about it.

I think research!rob modified this to do squashing of multiple blank
lines and printing of control characters as ^X, but he wouldn't let me
give it out.

	Andy Tannenbaum   Masscomp Inc  Westford MA   (617) 692-6200 x274
<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>

#	include	<stdio.h>
#	include	<sys/types.h>
#	include	<sys/stat.h>

/* tac.c (backwards cat) */

main(argc, argv)
int	argc;
char	**argv;
{

	register char	*p, *q;
	register int	i;

	struct	stat	st;
	off_t		off, lseek();
	char		buffer[ 1 + BUFSIZ + BUFSIZ + 1 ];
	char		*buf;
	char		*readerr = "Read error at offset %lu of %s\n";
	int		fd, any;
	void		output(), utcopyn();


	if (argc < 2) {
		(void) fprintf(stderr, "Usage: tac file ...\n");
		exit(1);
	}

	buf = buffer;
	*buf++ = '\n';


	any = 0;
	while (--argc) {
		if (stat(p = *++argv, &st) == -1) {
			(void) fprintf(stderr , "Bad status for %s\n", p);
			any++;
			continue;
		}
		if ((off = st.st_size) == 0) continue;
		if ((fd = open(p, 0)) == -1) {
			(void) fprintf(stderr, "Can't open %s\n", p);
			any++;
			continue;
		}

		if ((i = off % BUFSIZ ) == 0) i = BUFSIZ;
		off -= i;


		(void) lseek(fd, off, 0);
		if (read(fd, buf, i) != i) {
			(void) fprintf(stderr, readerr, off, p);
			(void) close(fd);
			any++;
			continue;
		}
		p = q = buf + i;
		if (*--p == '\n') q = p;


		for (;;) {
			while (*--p != '\n');
			if (p < buf) {
				if (off == 0) {
					output(p, q);
					(void) close(fd);
					break;
				}
				(void) lseek(fd, off -= BUFSIZ, 0);
				if ((i = q - buf) > BUFSIZ) {
					(void) fprintf(stderr, "Line too long\n");
					any++;
					i = BUFSIZ;
				}
				utcopyn(p = buf + BUFSIZ, buf, i);
				q = p + i;
				if (read(fd, buf, BUFSIZ) != BUFSIZ) {
					(void) fprintf(stderr, readerr, off, *argv);
					any++;
					(void) close(fd);
					break;
				}
				continue;
			}
			output(p, q);
			q = p;
		}
	}
	exit(any);
}


static void
output(p, q)
register char	*p,*q;
{
	register FILE	*iop;

	for (iop = stdout, p++; p < q; (void) putc(*p++, iop));
	(void) putc('\n', iop);
}


static void
utcopyn(tp, fp, n)
register char *tp, *fp;
register int n;
{
	while (--n >= 0) *tp++ = *fp++;
	return;
}

/* end of tac.c */

hansen@pegasus.UUCP (Tony L. Hansen) (09/24/84)

#!/bin/sh
# This is a shar archive.
# The rest of this file is a shell script which will extract:
# README2 README tac.1 tac.c
# Archive created: Sun Sep 23 23:55:39 EDT 1984
echo x - README2
sed 's/^X//' > README2 << '~FUNKY STUFF~'

< Tac does not read stdin.  Why?  Think about it.

Why not? If it's coming from a file, use it directly. If it's coming from a
pipe, just copy it into a temp file before using it. That's simple enough,
don't you think?

The other thing missing was a manual page. One follows, taken from the
cat(1) manual page.

Thanks for reposting the program Andy! I missed it the last time around.
Things are always better the second time around.

					Tony Hansen
					pegasus!hansen
~FUNKY STUFF~
ls -l README2
echo x - README
sed 's/^X//' > README << '~FUNKY STUFF~'
From hogpc!houti!ariel!vax135!cornell!uw-beaver!tektronix!hplabs!sdcrdcf!sdcsvax!dcdwest!ittvax!decvax!wivax!masscomp!trb Fri Sep 21 12:12:26 1984
Newsgroups: net.sources
Subject: tac.c a program to print lines in reverse order in linear time
Message-ID: <419@masscomp.UUCP>
Organization: MASSCOMP, Westford, MA
Lines: 126

This program was first posted to net.sources in Oct 1982.  What comes
around goes around, I always say, so here it is, tac.c.

Tac does not read stdin.  Why?  Think about it.

I think research!rob modified this to do squashing of multiple blank
lines and printing of control characters as ^X, but he wouldn't let me
give it out.

	Andy Tannenbaum   Masscomp Inc  Westford MA   (617) 692-6200 x274
<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
~FUNKY STUFF~
ls -l README
echo x - tac.1
sed 's/^X//' > tac.1 << '~FUNKY STUFF~'
X.TH TAC 1
X.SH NAME
tac \- concatenate and print files in reverse
X.SH SYNOPSIS
X.B tac
file .\|.\|.
X.SH DESCRIPTION
X.I Tac\^
reads each
X.I file\^
in sequence
and writes it on the standard output with the lines in reverse order.
Thus:
X.PP
X.RS
tac file
X.RE
X.PP
prints the file in reverse, and:
X.PP
X.RS
tac file1 file2 >file3
X.RE
X.PP
concatenates the first two files, reversed, and places the result on the
third.
X.PP
X.RS
tac file | tac
X.RE
will reproduce the contents of the file.
X.PP
If no input file is given,
or if the argument
X.B \-
is encountered,
X.I tac\^
reads from the standard input.
X.SH WARNING
Command formats such as
X.RS
tac file1 file2 >file1
X.RE
will cause
the original data in \fIfile1\fP to be lost,
therefore, take care when using shell special characters.
X.SH SEE ALSO
cat(1).
X.\"	@(#)tac.1	1.1 @(#)
~FUNKY STUFF~
ls -l tac.1
echo x - tac.c
sed 's/^X//' > tac.c << '~FUNKY STUFF~'
#ifndef lint
char SCCS[] = "@(#)tac.c	1.5 @(#)";
#endif lint

#	include	<stdio.h>
#	include	<sys/types.h>
#	include	<sys/stat.h>
#	include <errno.h>

/* tac.c (backwards cat) */

static char	*progname;
static int	errorcount = 0;
static char	readerr[] = "%s: Read error at offset %lu of %s.\n";
extern off_t	lseek();

main(argc, argv)
int	argc;
char	**argv;
{

	char		*filename;
	struct	stat	st;
	int		fd;
	int		rmstdin = 0;
	void		output(), utcopyn(), reverse();
	char		*copystdin();

	progname = argv[0];

	/* no arguments means to read stdin */
	if (argc == 1) {
		argv[1] = "-";
		argc = 2;
	}

	while (--argc > 0) {
		filename = *++argv;

		/* "-" means to read stdin */
		if (filename[0] == '-' && !filename[1])
			/* if it's a pipe, then copy to a temp file */
			if ((lseek(fileno(stdin), 0, 0) == -1) &&
			    (errno == ESPIPE)) {
				if ((filename = copystdin()) == 0)
					continue;
				rmstdin = 1;
			} else { /* stdin's a file, so use it directly */
				if (fstat(fileno(stdin), &st) == -1) {
					(void) fprintf(stderr,
						"%s: Bad status for stdin.\n",
						progname);
					errorcount++;
					continue;
				}

				/* empty file? why bother. */
				if (st.st_size == 0)
					continue;
				reverse(fileno(stdin), st.st_size, "stdin");
				continue;
			}

		/* no file? */
		if (stat(filename, &st) == -1) {
			(void) fprintf(stderr , "%s: Bad status for %s\n",
				progname, filename);
			errorcount++;
			continue;
		}

		/* empty file? why bother. */
		if (st.st_size == 0)
			continue;

		/* open it up */
		if ((fd = open(filename, 0)) == -1) {
			(void) fprintf(stderr, "%s: Can't open %s\n",
				progname, filename);
			errorcount++;
			continue;
		}
		
		/* reverse it */
		reverse(fd, st.st_size, filename);
		
		/* if it's a temp file from a pipe, remove it */
		if (rmstdin) {
			(void) unlink(filename);
			rmstdin = 0;
		}
	}
	exit(errorcount);
}


void
reverse (fd, off, filename)
register int	fd;
register off_t	off;
char *filename;
{
	register int	i;
	register char	*p, *q;
	char		buffer[ 1 + BUFSIZ + BUFSIZ + 1 ];
	char		*buf;

	/* initialize buffer */
	buf = buffer;
	*buf++ = '\n';

	if ((i = off % BUFSIZ ) == 0)
		i = BUFSIZ;
	off -= i;

	(void) lseek(fd, off, 0);
	if (read(fd, buf, i) != i) {
		(void) fprintf(stderr, readerr, progname, off,
			filename);
		(void) close(fd);
		errorcount++;
		return;
	}

	p = q = buf + i;
	if (*--p == '\n')
		q = p;

	for (;;) {
		while (*--p != '\n')
			;
		if (p < buf) {
			if (off == 0) {
				output(p, q);
				(void) close(fd);
				return;
			}
			(void) lseek(fd, off -= BUFSIZ, 0);
			if ((i = q - buf) > BUFSIZ) {
				(void) fprintf(stderr,
					"%s: Line too long in %s.\n",
					progname, filename);
				errorcount++;
				i = BUFSIZ;
			}
			utcopyn(p = buf + BUFSIZ, buf, i);
			q = p + i;
			if (read(fd, buf, BUFSIZ) != BUFSIZ) {
				(void) fprintf(stderr, readerr,
					progname, off, filename);
				errorcount++;
				(void) close(fd);
				return;
			}
			continue;
		}
		output(p, q);
		q = p;
	}
}


static void
output(p, q)
register char	*p,*q;
{
	register FILE	*iop;

	for (iop = stdout, p++; p < q; (void) putc(*p++, iop))
		;
	(void) putc('\n', iop);
}


static void
utcopyn(tp, fp, n)
register char *tp, *fp;
register int n;
{
	while (--n >= 0)
		*tp++ = *fp++;
	return;
}


static char *template = "/tmp/tac.XXXXXX";	/* template for mktemp */

static char *
copystdin()
{
	char buf[BUFSIZ];
	int ofile, readcount;
	char *mktemp();

	(void) umask(066);
	(void) mktemp(template);
	ofile = creat(template, 0600);
	if (ofile < 0)
	    {
	    (void) fprintf(stderr,
		"%s: can't create temp file '%s' for use with stdin!\n",
			progname, template);
	    return 0;
	    }

	/* copy stdin to temp file */
	while ((readcount = read (fileno (stdin), buf, sizeof buf)) > 0)
	    (void) write (ofile, buf, (unsigned) readcount);

	(void) close(ofile);
	(void) fclose(stdin);

	return template;
}

/* end of tac.c */
~FUNKY STUFF~
ls -l tac.c
# The following exit is to ensure that extra garbage 
# after the end of the shar file will be ignored.
exit 0