xref: /freebsd/usr.bin/tail/reverse.c (revision 8a16b7a18f5d0b031f09832fd7752fba717e2a97)
19b50d902SRodney W. Grimes /*-
2*8a16b7a1SPedro F. Giffuni  * SPDX-License-Identifier: BSD-3-Clause
3*8a16b7a1SPedro F. Giffuni  *
49b50d902SRodney W. Grimes  * Copyright (c) 1991, 1993
59b50d902SRodney W. Grimes  *	The Regents of the University of California.  All rights reserved.
69b50d902SRodney W. Grimes  *
79b50d902SRodney W. Grimes  * This code is derived from software contributed to Berkeley by
89b50d902SRodney W. Grimes  * Edward Sze-Tyan Wang.
99b50d902SRodney W. Grimes  *
109b50d902SRodney W. Grimes  * Redistribution and use in source and binary forms, with or without
119b50d902SRodney W. Grimes  * modification, are permitted provided that the following conditions
129b50d902SRodney W. Grimes  * are met:
139b50d902SRodney W. Grimes  * 1. Redistributions of source code must retain the above copyright
149b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer.
159b50d902SRodney W. Grimes  * 2. Redistributions in binary form must reproduce the above copyright
169b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer in the
179b50d902SRodney W. Grimes  *    documentation and/or other materials provided with the distribution.
18fbbd9655SWarner Losh  * 3. Neither the name of the University nor the names of its contributors
199b50d902SRodney W. Grimes  *    may be used to endorse or promote products derived from this software
209b50d902SRodney W. Grimes  *    without specific prior written permission.
219b50d902SRodney W. Grimes  *
229b50d902SRodney W. Grimes  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
239b50d902SRodney W. Grimes  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
249b50d902SRodney W. Grimes  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
259b50d902SRodney W. Grimes  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
269b50d902SRodney W. Grimes  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
279b50d902SRodney W. Grimes  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
289b50d902SRodney W. Grimes  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
299b50d902SRodney W. Grimes  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
309b50d902SRodney W. Grimes  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
319b50d902SRodney W. Grimes  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
329b50d902SRodney W. Grimes  * SUCH DAMAGE.
339b50d902SRodney W. Grimes  */
349b50d902SRodney W. Grimes 
3513685eeeSDavid Malone #if 0
369b50d902SRodney W. Grimes #ifndef lint
3713685eeeSDavid Malone static char sccsid[] = "@(#)reverse.c	8.1 (Berkeley) 6/6/93";
3813685eeeSDavid Malone #endif /* not lint */
39ea7cc495SPhilippe Charnier #endif
409b50d902SRodney W. Grimes 
4113685eeeSDavid Malone #include <sys/cdefs.h>
4213685eeeSDavid Malone __FBSDID("$FreeBSD$");
4313685eeeSDavid Malone 
449b50d902SRodney W. Grimes #include <sys/param.h>
45cdb7a6fcSAlan Somers #include <sys/queue.h>
469b50d902SRodney W. Grimes #include <sys/stat.h>
479b50d902SRodney W. Grimes #include <sys/mman.h>
489b50d902SRodney W. Grimes 
49814e3a92SMark Murray #include <err.h>
509b50d902SRodney W. Grimes #include <errno.h>
51814e3a92SMark Murray #include <limits.h>
52d0990ea9SDavid Malone #include <stdint.h>
539b50d902SRodney W. Grimes #include <stdio.h>
549b50d902SRodney W. Grimes #include <stdlib.h>
559b50d902SRodney W. Grimes #include <string.h>
56814e3a92SMark Murray #include <unistd.h>
57814e3a92SMark Murray 
589b50d902SRodney W. Grimes #include "extern.h"
599b50d902SRodney W. Grimes 
6022da50cfSBrian Somers static void r_buf(FILE *, const char *);
6122da50cfSBrian Somers static void r_reg(FILE *, const char *, enum STYLE, off_t, struct stat *);
629b50d902SRodney W. Grimes 
639b50d902SRodney W. Grimes /*
649b50d902SRodney W. Grimes  * reverse -- display input in reverse order by line.
659b50d902SRodney W. Grimes  *
669b50d902SRodney W. Grimes  * There are six separate cases for this -- regular and non-regular
679b50d902SRodney W. Grimes  * files by bytes, lines or the whole file.
689b50d902SRodney W. Grimes  *
699b50d902SRodney W. Grimes  * BYTES	display N bytes
709b50d902SRodney W. Grimes  *	REG	mmap the file and display the lines
719b50d902SRodney W. Grimes  *	NOREG	cyclically read characters into a wrap-around buffer
729b50d902SRodney W. Grimes  *
739b50d902SRodney W. Grimes  * LINES	display N lines
749b50d902SRodney W. Grimes  *	REG	mmap the file and display the lines
759b50d902SRodney W. Grimes  *	NOREG	cyclically read lines into a wrap-around array of buffers
769b50d902SRodney W. Grimes  *
779b50d902SRodney W. Grimes  * FILE		display the entire file
789b50d902SRodney W. Grimes  *	REG	mmap the file and display the lines
799b50d902SRodney W. Grimes  *	NOREG	cyclically read input into a linked list of buffers
809b50d902SRodney W. Grimes  */
819b50d902SRodney W. Grimes void
8222da50cfSBrian Somers reverse(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
839b50d902SRodney W. Grimes {
849b50d902SRodney W. Grimes 	if (style != REVERSE && off == 0)
859b50d902SRodney W. Grimes 		return;
869b50d902SRodney W. Grimes 
879b50d902SRodney W. Grimes 	if (S_ISREG(sbp->st_mode))
8822da50cfSBrian Somers 		r_reg(fp, fn, style, off, sbp);
899b50d902SRodney W. Grimes 	else
909b50d902SRodney W. Grimes 		switch(style) {
919b50d902SRodney W. Grimes 		case FBYTES:
929b50d902SRodney W. Grimes 		case RBYTES:
9322da50cfSBrian Somers 			bytes(fp, fn, off);
949b50d902SRodney W. Grimes 			break;
959b50d902SRodney W. Grimes 		case FLINES:
969b50d902SRodney W. Grimes 		case RLINES:
9722da50cfSBrian Somers 			lines(fp, fn, off);
989b50d902SRodney W. Grimes 			break;
999b50d902SRodney W. Grimes 		case REVERSE:
10022da50cfSBrian Somers 			r_buf(fp, fn);
1019b50d902SRodney W. Grimes 			break;
102814e3a92SMark Murray 		default:
103b77b9b9aSMurray Stokely 			break;
1049b50d902SRodney W. Grimes 		}
1059b50d902SRodney W. Grimes }
1069b50d902SRodney W. Grimes 
1079b50d902SRodney W. Grimes /*
1089b50d902SRodney W. Grimes  * r_reg -- display a regular file in reverse order by line.
1099b50d902SRodney W. Grimes  */
1109b50d902SRodney W. Grimes static void
11122da50cfSBrian Somers r_reg(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
1129b50d902SRodney W. Grimes {
113726098d3SDavid Malone 	struct mapinfo map;
114726098d3SDavid Malone 	off_t curoff, size, lineend;
115726098d3SDavid Malone 	int i;
1169b50d902SRodney W. Grimes 
1179b50d902SRodney W. Grimes 	if (!(size = sbp->st_size))
1189b50d902SRodney W. Grimes 		return;
1199b50d902SRodney W. Grimes 
120726098d3SDavid Malone 	map.start = NULL;
121726098d3SDavid Malone 	map.mapoff = map.maxoff = size;
122726098d3SDavid Malone 	map.fd = fileno(fp);
1232277edc8SAlan Somers 	map.maplen = 0;
124726098d3SDavid Malone 
125726098d3SDavid Malone 	/*
126726098d3SDavid Malone 	 * Last char is special, ignore whether newline or not. Note that
127726098d3SDavid Malone 	 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
128726098d3SDavid Malone 	 */
129726098d3SDavid Malone 	curoff = size - 2;
130726098d3SDavid Malone 	lineend = size;
131726098d3SDavid Malone 	while (curoff >= 0) {
132d0990ea9SDavid Malone 		if (curoff < map.mapoff ||
133d0990ea9SDavid Malone 		    curoff >= map.mapoff + (off_t)map.maplen) {
134726098d3SDavid Malone 			if (maparound(&map, curoff) != 0) {
13522da50cfSBrian Somers 				ierr(fn);
1369b50d902SRodney W. Grimes 				return;
1379b50d902SRodney W. Grimes 			}
138726098d3SDavid Malone 		}
139726098d3SDavid Malone 		for (i = curoff - map.mapoff; i >= 0; i--) {
140726098d3SDavid Malone 			if (style == RBYTES && --off == 0)
141726098d3SDavid Malone 				break;
142726098d3SDavid Malone 			if (map.start[i] == '\n')
143726098d3SDavid Malone 				break;
144726098d3SDavid Malone 		}
145726098d3SDavid Malone 		/* `i' is either the map offset of a '\n', or -1. */
146726098d3SDavid Malone 		curoff = map.mapoff + i;
147726098d3SDavid Malone 		if (i < 0)
148726098d3SDavid Malone 			continue;
1499b50d902SRodney W. Grimes 
150726098d3SDavid Malone 		/* Print the line and update offsets. */
151726098d3SDavid Malone 		if (mapprint(&map, curoff + 1, lineend - curoff - 1) != 0) {
15222da50cfSBrian Somers 			ierr(fn);
1539b50d902SRodney W. Grimes 			return;
1549b50d902SRodney W. Grimes 		}
155726098d3SDavid Malone 		lineend = curoff + 1;
156726098d3SDavid Malone 		curoff--;
1579b50d902SRodney W. Grimes 
158726098d3SDavid Malone 		if (style == RLINES)
159726098d3SDavid Malone 			off--;
1609b50d902SRodney W. Grimes 
161726098d3SDavid Malone 		if (off == 0 && style != REVERSE) {
162726098d3SDavid Malone 			/* Avoid printing anything below. */
163726098d3SDavid Malone 			curoff = 0;
1649b50d902SRodney W. Grimes 			break;
1659b50d902SRodney W. Grimes 		}
1669b50d902SRodney W. Grimes 	}
167726098d3SDavid Malone 	if (curoff < 0 && mapprint(&map, 0, lineend) != 0) {
16822da50cfSBrian Somers 		ierr(fn);
169726098d3SDavid Malone 		return;
170726098d3SDavid Malone 	}
171726098d3SDavid Malone 	if (map.start != NULL && munmap(map.start, map.maplen))
17222da50cfSBrian Somers 		ierr(fn);
1739b50d902SRodney W. Grimes }
1749b50d902SRodney W. Grimes 
17543e9ad02SAlan Somers #define BSZ	(128 * 1024)
176cdb7a6fcSAlan Somers typedef struct bfelem {
177cdb7a6fcSAlan Somers 	TAILQ_ENTRY(bfelem) entries;
178cdb7a6fcSAlan Somers 	size_t len;
17943e9ad02SAlan Somers 	char l[BSZ];
180cdb7a6fcSAlan Somers } bfelem_t;
1819b50d902SRodney W. Grimes 
1829b50d902SRodney W. Grimes /*
1839b50d902SRodney W. Grimes  * r_buf -- display a non-regular file in reverse order by line.
1849b50d902SRodney W. Grimes  *
1859b50d902SRodney W. Grimes  * This is the function that saves the entire input, storing the data in a
1869b50d902SRodney W. Grimes  * doubly linked list of buffers and then displays them in reverse order.
1879b50d902SRodney W. Grimes  * It has the usual nastiness of trying to find the newlines, as there's no
1889b50d902SRodney W. Grimes  * guarantee that a newline occurs anywhere in the file, let alone in any
1899b50d902SRodney W. Grimes  * particular buffer.  If we run out of memory, input is discarded (and the
1909b50d902SRodney W. Grimes  * user warned).
1919b50d902SRodney W. Grimes  */
1929b50d902SRodney W. Grimes static void
19322da50cfSBrian Somers r_buf(FILE *fp, const char *fn)
1949b50d902SRodney W. Grimes {
19543e9ad02SAlan Somers 	struct bfelem *tl, *first = NULL;
19643e9ad02SAlan Somers 	size_t llen;
19748a1ef22SJeroen Ruigrok van der Werven 	char *p;
198cdb7a6fcSAlan Somers 	off_t enomem = 0;
199cdb7a6fcSAlan Somers 	TAILQ_HEAD(bfhead, bfelem) head;
2009b50d902SRodney W. Grimes 
201cdb7a6fcSAlan Somers 	TAILQ_INIT(&head);
202cdb7a6fcSAlan Somers 
203cdb7a6fcSAlan Somers 	while (!feof(fp)) {
20443e9ad02SAlan Somers 		size_t len;
20543e9ad02SAlan Somers 
2069b50d902SRodney W. Grimes 		/*
2079b50d902SRodney W. Grimes 		 * Allocate a new block and link it into place in a doubly
2089b50d902SRodney W. Grimes 		 * linked list.  If out of memory, toss the LRU block and
2099b50d902SRodney W. Grimes 		 * keep going.
2109b50d902SRodney W. Grimes 		 */
211cdb7a6fcSAlan Somers 		while ((tl = malloc(sizeof(bfelem_t))) == NULL) {
212cdb7a6fcSAlan Somers 			first = TAILQ_FIRST(&head);
213cdb7a6fcSAlan Somers 			if (TAILQ_EMPTY(&head))
214ac551270SPeter Wemm 				err(1, "malloc");
215cdb7a6fcSAlan Somers 			enomem += first->len;
216cdb7a6fcSAlan Somers 			TAILQ_REMOVE(&head, first, entries);
217cdb7a6fcSAlan Somers 			free(first);
2182277edc8SAlan Somers 		}
219cdb7a6fcSAlan Somers 		TAILQ_INSERT_TAIL(&head, tl, entries);
2209b50d902SRodney W. Grimes 
2219b50d902SRodney W. Grimes 		/* Fill the block with input data. */
222cdb7a6fcSAlan Somers 		len = 0;
22343e9ad02SAlan Somers 		while ((!feof(fp)) && len < BSZ) {
224cdb7a6fcSAlan Somers 			p = tl->l + len;
22543e9ad02SAlan Somers 			len += fread(p, 1, BSZ - len, fp);
22649a598abSAdam David 			if (ferror(fp)) {
22722da50cfSBrian Somers 				ierr(fn);
22849a598abSAdam David 				return;
22949a598abSAdam David 			}
2309b50d902SRodney W. Grimes 		}
2319b50d902SRodney W. Grimes 
2329b50d902SRodney W. Grimes 		tl->len = len;
2339b50d902SRodney W. Grimes 	}
2349b50d902SRodney W. Grimes 
2359b50d902SRodney W. Grimes 	if (enomem) {
236d0990ea9SDavid Malone 		warnx("warning: %jd bytes discarded", (intmax_t)enomem);
2379b50d902SRodney W. Grimes 		rval = 1;
2389b50d902SRodney W. Grimes 	}
2399b50d902SRodney W. Grimes 
2409b50d902SRodney W. Grimes 	/*
241cdb7a6fcSAlan Somers 	 * Now print the lines in reverse order
242cdb7a6fcSAlan Somers 	 * Outline:
243cdb7a6fcSAlan Somers 	 *    Scan backward for "\n",
244cdb7a6fcSAlan Somers 	 *    print forward to the end of the buffers
245cdb7a6fcSAlan Somers 	 *    free any buffers that start after the "\n" just found
246cdb7a6fcSAlan Somers 	 *    Loop
2479b50d902SRodney W. Grimes 	 */
248cdb7a6fcSAlan Somers 	tl = TAILQ_LAST(&head, bfhead);
249cdb7a6fcSAlan Somers 	first = TAILQ_FIRST(&head);
250cdb7a6fcSAlan Somers 	while (tl != NULL) {
25143e9ad02SAlan Somers 		struct bfelem *temp;
25243e9ad02SAlan Somers 
253cdb7a6fcSAlan Somers 		for (p = tl->l + tl->len - 1, llen = 0; p >= tl->l;
254cdb7a6fcSAlan Somers 		    --p, ++llen) {
255cdb7a6fcSAlan Somers 			int start = (tl == first && p == tl->l);
256cdb7a6fcSAlan Somers 
257cdb7a6fcSAlan Somers 			if ((*p == '\n') || start) {
258cdb7a6fcSAlan Somers 				struct bfelem *tr;
259cdb7a6fcSAlan Somers 
26043e9ad02SAlan Somers 				if (start && llen)
261cdb7a6fcSAlan Somers 					WR(p, llen + 1);
262cdb7a6fcSAlan Somers 				else if (llen)
2639b50d902SRodney W. Grimes 					WR(p + 1, llen);
264cdb7a6fcSAlan Somers 				tr = TAILQ_NEXT(tl, entries);
2659b50d902SRodney W. Grimes 				llen = 0;
266cdb7a6fcSAlan Somers 				if (tr != NULL) {
267cdb7a6fcSAlan Somers 					TAILQ_FOREACH_FROM_SAFE(tr, &head,
268cdb7a6fcSAlan Somers 					    entries, temp) {
269cdb7a6fcSAlan Somers 						if (tr->len)
270cdb7a6fcSAlan Somers 							WR(&tr->l, tr->len);
271cdb7a6fcSAlan Somers 						TAILQ_REMOVE(&head, tr,
272cdb7a6fcSAlan Somers 						    entries);
273cdb7a6fcSAlan Somers 						free(tr);
2749b50d902SRodney W. Grimes 					}
275cdb7a6fcSAlan Somers 				}
2769b50d902SRodney W. Grimes 			}
2779b50d902SRodney W. Grimes 		}
2789b50d902SRodney W. Grimes 		tl->len = llen;
279cdb7a6fcSAlan Somers 		tl = TAILQ_PREV(tl, bfhead, entries);
2809b50d902SRodney W. Grimes 	}
281cdb7a6fcSAlan Somers 	TAILQ_REMOVE(&head, first, entries);
282cdb7a6fcSAlan Somers 	free(first);
2839b50d902SRodney W. Grimes }
284