xref: /freebsd/usr.bin/tail/reverse.c (revision b70e57be2cfe83ec9f410e2f317ea38aaac61a98)
19b50d902SRodney W. Grimes /*-
28a16b7a1SPedro F. Giffuni  * SPDX-License-Identifier: BSD-3-Clause
38a16b7a1SPedro 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 
359b50d902SRodney W. Grimes #include <sys/param.h>
36cdb7a6fcSAlan Somers #include <sys/queue.h>
379b50d902SRodney W. Grimes #include <sys/stat.h>
389b50d902SRodney W. Grimes #include <sys/mman.h>
399b50d902SRodney W. Grimes 
40814e3a92SMark Murray #include <err.h>
419b50d902SRodney W. Grimes #include <errno.h>
42814e3a92SMark Murray #include <limits.h>
43d0990ea9SDavid Malone #include <stdint.h>
449b50d902SRodney W. Grimes #include <stdio.h>
459b50d902SRodney W. Grimes #include <stdlib.h>
469b50d902SRodney W. Grimes #include <string.h>
47814e3a92SMark Murray #include <unistd.h>
48814e3a92SMark Murray 
49c851fce6SMariusz Zaborski #include <libcasper.h>
50c851fce6SMariusz Zaborski #include <casper/cap_fileargs.h>
51c851fce6SMariusz Zaborski 
529b50d902SRodney W. Grimes #include "extern.h"
539b50d902SRodney W. Grimes 
5422da50cfSBrian Somers static void r_buf(FILE *, const char *);
5522da50cfSBrian Somers static void r_reg(FILE *, const char *, enum STYLE, off_t, struct stat *);
569b50d902SRodney W. Grimes 
579b50d902SRodney W. Grimes /*
589b50d902SRodney W. Grimes  * reverse -- display input in reverse order by line.
599b50d902SRodney W. Grimes  *
609b50d902SRodney W. Grimes  * There are six separate cases for this -- regular and non-regular
619b50d902SRodney W. Grimes  * files by bytes, lines or the whole file.
629b50d902SRodney W. Grimes  *
639b50d902SRodney W. Grimes  * BYTES	display N bytes
649b50d902SRodney W. Grimes  *	REG	mmap the file and display the lines
659b50d902SRodney W. Grimes  *	NOREG	cyclically read characters into a wrap-around buffer
669b50d902SRodney W. Grimes  *
679b50d902SRodney W. Grimes  * LINES	display N lines
689b50d902SRodney W. Grimes  *	REG	mmap the file and display the lines
699b50d902SRodney W. Grimes  *	NOREG	cyclically read lines into a wrap-around array of buffers
709b50d902SRodney W. Grimes  *
719b50d902SRodney W. Grimes  * FILE		display the entire file
729b50d902SRodney W. Grimes  *	REG	mmap the file and display the lines
739b50d902SRodney W. Grimes  *	NOREG	cyclically read input into a linked list of buffers
749b50d902SRodney W. Grimes  */
759b50d902SRodney W. Grimes void
reverse(FILE * fp,const char * fn,enum STYLE style,off_t off,struct stat * sbp)7622da50cfSBrian Somers reverse(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
779b50d902SRodney W. Grimes {
789b50d902SRodney W. Grimes 	if (style != REVERSE && off == 0)
799b50d902SRodney W. Grimes 		return;
809b50d902SRodney W. Grimes 
819b50d902SRodney W. Grimes 	if (S_ISREG(sbp->st_mode))
8222da50cfSBrian Somers 		r_reg(fp, fn, style, off, sbp);
839b50d902SRodney W. Grimes 	else
849b50d902SRodney W. Grimes 		switch(style) {
859b50d902SRodney W. Grimes 		case FBYTES:
869b50d902SRodney W. Grimes 		case RBYTES:
8722da50cfSBrian Somers 			bytes(fp, fn, off);
889b50d902SRodney W. Grimes 			break;
899b50d902SRodney W. Grimes 		case FLINES:
909b50d902SRodney W. Grimes 		case RLINES:
9122da50cfSBrian Somers 			lines(fp, fn, off);
929b50d902SRodney W. Grimes 			break;
939b50d902SRodney W. Grimes 		case REVERSE:
9422da50cfSBrian Somers 			r_buf(fp, fn);
959b50d902SRodney W. Grimes 			break;
96814e3a92SMark Murray 		default:
97b77b9b9aSMurray Stokely 			break;
989b50d902SRodney W. Grimes 		}
999b50d902SRodney W. Grimes }
1009b50d902SRodney W. Grimes 
1019b50d902SRodney W. Grimes /*
1029b50d902SRodney W. Grimes  * r_reg -- display a regular file in reverse order by line.
1039b50d902SRodney W. Grimes  */
1049b50d902SRodney W. Grimes static void
r_reg(FILE * fp,const char * fn,enum STYLE style,off_t off,struct stat * sbp)10522da50cfSBrian Somers r_reg(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
1069b50d902SRodney W. Grimes {
107726098d3SDavid Malone 	struct mapinfo map;
108726098d3SDavid Malone 	off_t curoff, size, lineend;
109726098d3SDavid Malone 	int i;
1109b50d902SRodney W. Grimes 
1119b50d902SRodney W. Grimes 	if (!(size = sbp->st_size))
1129b50d902SRodney W. Grimes 		return;
1139b50d902SRodney W. Grimes 
114726098d3SDavid Malone 	map.start = NULL;
115726098d3SDavid Malone 	map.mapoff = map.maxoff = size;
116726098d3SDavid Malone 	map.fd = fileno(fp);
1172277edc8SAlan Somers 	map.maplen = 0;
118726098d3SDavid Malone 
119726098d3SDavid Malone 	/*
120726098d3SDavid Malone 	 * Last char is special, ignore whether newline or not. Note that
121726098d3SDavid Malone 	 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
122726098d3SDavid Malone 	 */
123726098d3SDavid Malone 	curoff = size - 2;
124726098d3SDavid Malone 	lineend = size;
125726098d3SDavid Malone 	while (curoff >= 0) {
126d0990ea9SDavid Malone 		if (curoff < map.mapoff ||
127d0990ea9SDavid Malone 		    curoff >= map.mapoff + (off_t)map.maplen) {
128726098d3SDavid Malone 			if (maparound(&map, curoff) != 0) {
12922da50cfSBrian Somers 				ierr(fn);
1309b50d902SRodney W. Grimes 				return;
1319b50d902SRodney W. Grimes 			}
132726098d3SDavid Malone 		}
133726098d3SDavid Malone 		for (i = curoff - map.mapoff; i >= 0; i--) {
134726098d3SDavid Malone 			if (style == RBYTES && --off == 0)
135726098d3SDavid Malone 				break;
136726098d3SDavid Malone 			if (map.start[i] == '\n')
137726098d3SDavid Malone 				break;
138726098d3SDavid Malone 		}
139726098d3SDavid Malone 		/* `i' is either the map offset of a '\n', or -1. */
140726098d3SDavid Malone 		curoff = map.mapoff + i;
141726098d3SDavid Malone 		if (i < 0)
142726098d3SDavid Malone 			continue;
1439b50d902SRodney W. Grimes 
144726098d3SDavid Malone 		/* Print the line and update offsets. */
145726098d3SDavid Malone 		if (mapprint(&map, curoff + 1, lineend - curoff - 1) != 0) {
14622da50cfSBrian Somers 			ierr(fn);
1479b50d902SRodney W. Grimes 			return;
1489b50d902SRodney W. Grimes 		}
149726098d3SDavid Malone 		lineend = curoff + 1;
150726098d3SDavid Malone 		curoff--;
1519b50d902SRodney W. Grimes 
152726098d3SDavid Malone 		if (style == RLINES)
153726098d3SDavid Malone 			off--;
1549b50d902SRodney W. Grimes 
155726098d3SDavid Malone 		if (off == 0 && style != REVERSE) {
156726098d3SDavid Malone 			/* Avoid printing anything below. */
157726098d3SDavid Malone 			curoff = 0;
1589b50d902SRodney W. Grimes 			break;
1599b50d902SRodney W. Grimes 		}
1609b50d902SRodney W. Grimes 	}
161726098d3SDavid Malone 	if (curoff < 0 && mapprint(&map, 0, lineend) != 0) {
16222da50cfSBrian Somers 		ierr(fn);
163726098d3SDavid Malone 		return;
164726098d3SDavid Malone 	}
165726098d3SDavid Malone 	if (map.start != NULL && munmap(map.start, map.maplen))
16622da50cfSBrian Somers 		ierr(fn);
1679b50d902SRodney W. Grimes }
1689b50d902SRodney W. Grimes 
16943e9ad02SAlan Somers #define BSZ	(128 * 1024)
170cdb7a6fcSAlan Somers typedef struct bfelem {
171cdb7a6fcSAlan Somers 	TAILQ_ENTRY(bfelem) entries;
172cdb7a6fcSAlan Somers 	size_t len;
17343e9ad02SAlan Somers 	char l[BSZ];
174cdb7a6fcSAlan Somers } bfelem_t;
1759b50d902SRodney W. Grimes 
1769b50d902SRodney W. Grimes /*
1779b50d902SRodney W. Grimes  * r_buf -- display a non-regular file in reverse order by line.
1789b50d902SRodney W. Grimes  *
1799b50d902SRodney W. Grimes  * This is the function that saves the entire input, storing the data in a
1809b50d902SRodney W. Grimes  * doubly linked list of buffers and then displays them in reverse order.
1819b50d902SRodney W. Grimes  * It has the usual nastiness of trying to find the newlines, as there's no
1829b50d902SRodney W. Grimes  * guarantee that a newline occurs anywhere in the file, let alone in any
1839b50d902SRodney W. Grimes  * particular buffer.  If we run out of memory, input is discarded (and the
1849b50d902SRodney W. Grimes  * user warned).
1859b50d902SRodney W. Grimes  */
1869b50d902SRodney W. Grimes static void
r_buf(FILE * fp,const char * fn)18722da50cfSBrian Somers r_buf(FILE *fp, const char *fn)
1889b50d902SRodney W. Grimes {
18943e9ad02SAlan Somers 	struct bfelem *tl, *first = NULL;
19043e9ad02SAlan Somers 	size_t llen;
19148a1ef22SJeroen Ruigrok van der Werven 	char *p;
192cdb7a6fcSAlan Somers 	off_t enomem = 0;
193cdb7a6fcSAlan Somers 	TAILQ_HEAD(bfhead, bfelem) head;
1949b50d902SRodney W. Grimes 
195cdb7a6fcSAlan Somers 	TAILQ_INIT(&head);
196cdb7a6fcSAlan Somers 
197cdb7a6fcSAlan Somers 	while (!feof(fp)) {
19843e9ad02SAlan Somers 		size_t len;
19943e9ad02SAlan Somers 
2009b50d902SRodney W. Grimes 		/*
2019b50d902SRodney W. Grimes 		 * Allocate a new block and link it into place in a doubly
2029b50d902SRodney W. Grimes 		 * linked list.  If out of memory, toss the LRU block and
2039b50d902SRodney W. Grimes 		 * keep going.
2049b50d902SRodney W. Grimes 		 */
205cdb7a6fcSAlan Somers 		while ((tl = malloc(sizeof(bfelem_t))) == NULL) {
206cdb7a6fcSAlan Somers 			first = TAILQ_FIRST(&head);
207cdb7a6fcSAlan Somers 			if (TAILQ_EMPTY(&head))
208*b70e57beSDag-Erling Smørgrav 				err(1, "failed to allocate memory");
209cdb7a6fcSAlan Somers 			enomem += first->len;
210cdb7a6fcSAlan Somers 			TAILQ_REMOVE(&head, first, entries);
211cdb7a6fcSAlan Somers 			free(first);
2122277edc8SAlan Somers 		}
213cdb7a6fcSAlan Somers 		TAILQ_INSERT_TAIL(&head, tl, entries);
2149b50d902SRodney W. Grimes 
2159b50d902SRodney W. Grimes 		/* Fill the block with input data. */
216cdb7a6fcSAlan Somers 		len = 0;
21743e9ad02SAlan Somers 		while ((!feof(fp)) && len < BSZ) {
218cdb7a6fcSAlan Somers 			p = tl->l + len;
21943e9ad02SAlan Somers 			len += fread(p, 1, BSZ - len, fp);
22049a598abSAdam David 			if (ferror(fp)) {
22122da50cfSBrian Somers 				ierr(fn);
22249a598abSAdam David 				return;
22349a598abSAdam David 			}
2249b50d902SRodney W. Grimes 		}
2259b50d902SRodney W. Grimes 
2269b50d902SRodney W. Grimes 		tl->len = len;
2279b50d902SRodney W. Grimes 	}
2289b50d902SRodney W. Grimes 
2299b50d902SRodney W. Grimes 	if (enomem) {
230d0990ea9SDavid Malone 		warnx("warning: %jd bytes discarded", (intmax_t)enomem);
2319b50d902SRodney W. Grimes 		rval = 1;
2329b50d902SRodney W. Grimes 	}
2339b50d902SRodney W. Grimes 
2349b50d902SRodney W. Grimes 	/*
235cdb7a6fcSAlan Somers 	 * Now print the lines in reverse order
236cdb7a6fcSAlan Somers 	 * Outline:
237cdb7a6fcSAlan Somers 	 *    Scan backward for "\n",
238cdb7a6fcSAlan Somers 	 *    print forward to the end of the buffers
239cdb7a6fcSAlan Somers 	 *    free any buffers that start after the "\n" just found
240cdb7a6fcSAlan Somers 	 *    Loop
2419b50d902SRodney W. Grimes 	 */
242cdb7a6fcSAlan Somers 	tl = TAILQ_LAST(&head, bfhead);
243cdb7a6fcSAlan Somers 	first = TAILQ_FIRST(&head);
244cdb7a6fcSAlan Somers 	while (tl != NULL) {
24543e9ad02SAlan Somers 		struct bfelem *temp;
24643e9ad02SAlan Somers 
247cdb7a6fcSAlan Somers 		for (p = tl->l + tl->len - 1, llen = 0; p >= tl->l;
248cdb7a6fcSAlan Somers 		    --p, ++llen) {
249cdb7a6fcSAlan Somers 			int start = (tl == first && p == tl->l);
250cdb7a6fcSAlan Somers 
251cdb7a6fcSAlan Somers 			if ((*p == '\n') || start) {
252cdb7a6fcSAlan Somers 				struct bfelem *tr;
253cdb7a6fcSAlan Somers 
254d23662ecSAlan Somers 				if (llen && start && *p != '\n')
255cdb7a6fcSAlan Somers 					WR(p, llen + 1);
256d23662ecSAlan Somers 				else if (llen) {
2579b50d902SRodney W. Grimes 					WR(p + 1, llen);
258d23662ecSAlan Somers 					if (start && *p == '\n')
259d23662ecSAlan Somers 						WR(p, 1);
260d23662ecSAlan Somers 				}
261cdb7a6fcSAlan Somers 				tr = TAILQ_NEXT(tl, entries);
2629b50d902SRodney W. Grimes 				llen = 0;
263cdb7a6fcSAlan Somers 				if (tr != NULL) {
264cdb7a6fcSAlan Somers 					TAILQ_FOREACH_FROM_SAFE(tr, &head,
265cdb7a6fcSAlan Somers 					    entries, temp) {
266cdb7a6fcSAlan Somers 						if (tr->len)
267cdb7a6fcSAlan Somers 							WR(&tr->l, tr->len);
268cdb7a6fcSAlan Somers 						TAILQ_REMOVE(&head, tr,
269cdb7a6fcSAlan Somers 						    entries);
270cdb7a6fcSAlan Somers 						free(tr);
2719b50d902SRodney W. Grimes 					}
272cdb7a6fcSAlan Somers 				}
2739b50d902SRodney W. Grimes 			}
2749b50d902SRodney W. Grimes 		}
2759b50d902SRodney W. Grimes 		tl->len = llen;
276cdb7a6fcSAlan Somers 		tl = TAILQ_PREV(tl, bfhead, entries);
2779b50d902SRodney W. Grimes 	}
278cdb7a6fcSAlan Somers 	TAILQ_REMOVE(&head, first, entries);
279cdb7a6fcSAlan Somers 	free(first);
2809b50d902SRodney W. Grimes }
281