diff --git a/usr.bin/head/Makefile b/usr.bin/head/Makefile index b966f8165795..0c9056924969 100644 --- a/usr.bin/head/Makefile +++ b/usr.bin/head/Makefile @@ -1,17 +1,18 @@ # @(#)Makefile 8.1 (Berkeley) 6/6/93 # $FreeBSD$ .include PROG= head +LIBADD= util HAS_TESTS= SUBDIR.${MK_TESTS}+= tests .if ${MK_CASPER} != "no" && !defined(RESCUE) LIBADD+= casper LIBADD+= cap_fileargs CFLAGS+=-DWITH_CASPER .endif .include diff --git a/usr.bin/head/head.1 b/usr.bin/head/head.1 index c74983822916..3968e7283b17 100644 --- a/usr.bin/head/head.1 +++ b/usr.bin/head/head.1 @@ -1,90 +1,108 @@ .\" Copyright (c) 1980, 1990, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" @(#)head.1 8.1 (Berkeley) 6/6/93 .\" $FreeBSD$ .\" -.Dd April 10, 2018 +.Dd June 12, 2022 .Dt HEAD 1 .Os .Sh NAME .Nm head .Nd display first lines of a file .Sh SYNOPSIS .Nm +.Op Fl qv .Op Fl n Ar count | Fl c Ar bytes .Op Ar .Sh DESCRIPTION This filter displays the first .Ar count lines or .Ar bytes of each of the specified files, or of the standard input if no files are specified. If .Ar count is omitted it defaults to 10. .Pp The following options are available: .Bl -tag -width indent .It Fl c Ar bytes , Fl -bytes Ns = Ns Ar bytes Print .Ar bytes of each of the specified files. .It Fl n Ar count , Fl -lines Ns = Ns Ar count Print .Ar count lines of each of the specified files. +.Pp +Both +.Ar count +and +.Ar bytes +may also be specified with size suffixes supported by +.Xr expand_number 3 . +.It Fl q , Fl -quiet , Fl -silent +Suppresses printing of headers when multiple files are being examined. +.It Fl v , Fl -verbose +Prepend each file with a header. .El .Pp -If more than a single file is specified, each file is preceded by a +If more than a single file is specified, or if the +.Fl v +option is used, each file is preceded by a header consisting of the string .Dq ==> XXX <== where .Dq XXX is the name of the file. +The +.Fl q +flag disables the printing of the header in all cases. .Sh EXIT STATUS .Ex -std .Sh EXAMPLES To display the first 500 lines of the file .Ar foo : .Pp .Dl $ head -n 500 foo .Pp .Nm can be used in conjunction with .Xr tail 1 in the following way to, for example, display only line 500 from the file .Ar foo : .Pp .Dl $ head -n 500 foo | tail -n 1 .Sh SEE ALSO -.Xr tail 1 +.Xr tail 1 , +.Xr expand_number 3 .Sh HISTORY The .Nm command appeared in PWB UNIX. diff --git a/usr.bin/head/head.c b/usr.bin/head/head.c index 0226dd96d9a6..1c6368823e7c 100644 --- a/usr.bin/head/head.c +++ b/usr.bin/head/head.c @@ -1,215 +1,228 @@ /* * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1980, 1987, 1992, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1980, 1987, 1992, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)head.c 8.2 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include +#include + #include #include /* * head - give the first few lines of a stream or of each of a set of files * * Bill Joy UCB August 24, 1977 */ -static void head(FILE *, int); +static void head(FILE *, intmax_t); static void head_bytes(FILE *, off_t); static void obsolete(char *[]); static void usage(void); static const struct option long_opts[] = { {"bytes", required_argument, NULL, 'c'}, {"lines", required_argument, NULL, 'n'}, + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 'q'}, + {"verbose", no_argument, NULL, 'v'}, {NULL, no_argument, NULL, 0} }; int main(int argc, char *argv[]) { FILE *fp; - char *ep; off_t bytecnt; - int ch, first, linecnt, eval; + intmax_t linecnt; + int ch, first, eval; fileargs_t *fa; cap_rights_t rights; + int qflag = 0; + int vflag = 0; linecnt = -1; eval = 0; bytecnt = -1; obsolete(argv); - while ((ch = getopt_long(argc, argv, "+n:c:", long_opts, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "+n:c:qv", long_opts, NULL)) != -1) { switch(ch) { case 'c': - bytecnt = strtoimax(optarg, &ep, 10); - if (*ep || bytecnt <= 0) + if (expand_number(optarg, &bytecnt) || bytecnt <= 0) errx(1, "illegal byte count -- %s", optarg); break; case 'n': - linecnt = strtol(optarg, &ep, 10); - if (*ep || linecnt <= 0) + if (expand_number(optarg, &linecnt) || linecnt <= 0) errx(1, "illegal line count -- %s", optarg); break; + case 'q': + qflag = 1; + vflag = 0; + break; + case 'v': + qflag = 0; + vflag = 1; + break; case '?': default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; fa = fileargs_init(argc, argv, O_RDONLY, 0, cap_rights_init(&rights, CAP_READ, CAP_FSTAT, CAP_FCNTL), FA_OPEN); if (fa == NULL) err(1, "unable to init casper"); caph_cache_catpages(); if (caph_limit_stdio() < 0 || caph_enter_casper() < 0) err(1, "unable to enter capability mode"); if (linecnt != -1 && bytecnt != -1) errx(1, "can't combine line and byte counts"); if (linecnt == -1) linecnt = 10; if (*argv != NULL) { for (first = 1; *argv != NULL; ++argv) { if ((fp = fileargs_fopen(fa, *argv, "r")) == NULL) { warn("%s", *argv); eval = 1; continue; } - if (argc > 1) { + if (vflag || (qflag == 0 && argc > 1)) { (void)printf("%s==> %s <==\n", first ? "" : "\n", *argv); first = 0; } if (bytecnt == -1) head(fp, linecnt); else head_bytes(fp, bytecnt); (void)fclose(fp); } } else if (bytecnt == -1) head(stdin, linecnt); else head_bytes(stdin, bytecnt); fileargs_free(fa); exit(eval); } static void -head(FILE *fp, int cnt) +head(FILE *fp, intmax_t cnt) { char *cp; size_t error, readlen; while (cnt != 0 && (cp = fgetln(fp, &readlen)) != NULL) { error = fwrite(cp, sizeof(char), readlen, stdout); if (error != readlen) err(1, "stdout"); cnt--; } } static void head_bytes(FILE *fp, off_t cnt) { char buf[4096]; size_t readlen; while (cnt) { if ((uintmax_t)cnt < sizeof(buf)) readlen = cnt; else readlen = sizeof(buf); readlen = fread(buf, sizeof(char), readlen, fp); if (readlen == 0) break; if (fwrite(buf, sizeof(char), readlen, stdout) != readlen) err(1, "stdout"); cnt -= readlen; } } static void obsolete(char *argv[]) { char *ap; while ((ap = *++argv)) { /* Return if "--" or not "-[0-9]*". */ if (ap[0] != '-' || ap[1] == '-' || !isdigit(ap[1])) return; if ((ap = malloc(strlen(*argv) + 2)) == NULL) err(1, NULL); ap[0] = '-'; ap[1] = 'n'; (void)strcpy(ap + 2, *argv + 1); *argv = ap; } } static void usage(void) { (void)fprintf(stderr, "usage: head [-n lines | -c bytes] [file ...]\n"); exit(1); } diff --git a/usr.bin/head/tests/head_test.sh b/usr.bin/head/tests/head_test.sh index 1fc3cb10b455..9d1b97507c12 100755 --- a/usr.bin/head/tests/head_test.sh +++ b/usr.bin/head/tests/head_test.sh @@ -1,132 +1,177 @@ # Copyright (c) 2017 Fred Schlechter # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $FreeBSD$ atf_test_case empty_file empty_file_head() { atf_set "descr" "Test head(1)'s handling of an empty file" } empty_file_body() { touch infile expectfile head infile > outfile head < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case default_no_options default_no_options_head() { atf_set "descr" "Test head(1)'s default mode" } default_no_options_body() { #head(1) is supposed to default to 10 lines of output. Verify that it does that. jot -b test 10 > expectfile jot -b test 100 > infile head infile > outfile atf_check -e empty cmp expectfile outfile } atf_test_case line_count line_count_head() { atf_set "descr" "Test head(1)'s -n option" } line_count_body() { jot -b test 100 > outfile head -n 50 outfile > expectfile atf_check -o inline:" 50 expectfile\n" wc -l expectfile } atf_test_case byte_count byte_count_head() { atf_set "descr" "Test head(1)'s -c option" } byte_count_body() { jot -b test 100 > outfile head -c 50 outfile > expectfile atf_check -o inline:" 50 expectfile\n" wc -c expectfile } atf_test_case sparse_file_text_at_beginning sparse_file_text_at_beginning_head() { atf_set "descr" "Test head(1)'s handling of a sparse file with text at the beginning of the file" } sparse_file_text_at_beginning_body () { jot -b test 10 > outfile truncate -s +1K outfile head -c 512 outfile > expectfile atf_check -o inline:" 512 expectfile\n" wc -c expectfile } atf_test_case sparse_file_text_at_end sparse_file_text_at_end_head() { atf_set "descr" "Test head(1)'s handling of a sparse file with text at the end of the file" } sparse_file_text_at_end_body () { truncate -s +1K infile echo test >> infile head -c 4096 < infile > outpipe atf_check cmp infile outpipe } atf_test_case missing_line_count missing_line_count_head() { atf_set "descr" "Test head(1)'s handling of a missing line count arg" } missing_line_count_body () { jot -b test 100 > outfile atf_check -s not-exit:0 -e not-empty head -n outfile } atf_test_case invalid_line_count invalid_line_count_head() { atf_set "descr" "Test head(1)'s handling of an invalid line count arg" } invalid_line_count_body () { jot -b test 100 > outfile atf_check -s not-exit:0 -e not-empty head -n -10 outfile } atf_test_case read_from_stdin read_from_stdin_head() { atf_set "descr" "Test head(1)'s reading of stdin" } read_from_stdin_body() { #head(1) defaults to head -n 10 if no args are given. jot -b test 10 > outfile jot -b test 20 | head > expectfile atf_check cmp outfile expectfile } +atf_test_case silent_header +silent_header_head() { + atf_set "descr" "Test head(1)'s silent header feature" +} +silent_header_body() { + #head(1) defaults to head -n 10 if no args are given. + jot 11 1 11 > file1 + jot 11 2 12 > file2 + jot 10 1 10 > expectfile + jot 10 2 11 >> expectfile + head -q file1 file2 > outfile + atf_check cmp outfile expectfile +} + +atf_test_case verbose_header +verbose_header_head() { + atf_set "descr" "Test head(1)'s verbose header feature" +} +verbose_header_body() { + #head(1) defaults to head -n 10 if no args are given. + jot -b test 10 > file1 + echo '==> file1 <==' > expectfile + cat file1 >> expectfile + head -v file1 > outfile + atf_check cmp outfile expectfile +} + +atf_test_case si_number +si_number_head() { + atf_set "descr" "Test head(1)'s SI number feature" +} +si_number_body() { + jot -b aaaaaaa 129 > file1 + jot -b aaaaaaa 128 > expectfile + head -c 1k file1 > outfile + atf_check cmp outfile expectfile + jot 1025 1 1025 > file1 + jot 1024 1 1024 > expectfile + head -n 1k file1 > outfile + atf_check cmp outfile expectfile +} + atf_init_test_cases() { atf_add_test_case empty_file atf_add_test_case default_no_options atf_add_test_case line_count atf_add_test_case byte_count atf_add_test_case sparse_file_text_at_beginning atf_add_test_case sparse_file_text_at_end atf_add_test_case missing_line_count atf_add_test_case invalid_line_count atf_add_test_case read_from_stdin + atf_add_test_case silent_header + atf_add_test_case verbose_header + atf_add_test_case si_number } diff --git a/usr.bin/tail/Makefile b/usr.bin/tail/Makefile index 5d53b23957a7..b53d9d2c215f 100644 --- a/usr.bin/tail/Makefile +++ b/usr.bin/tail/Makefile @@ -1,18 +1,19 @@ # $FreeBSD$ # @(#)Makefile 8.1 (Berkeley) 6/6/93 .include PROG= tail SRCS= forward.c misc.c read.c reverse.c tail.c +LIBADD= util .if ${MK_CASPER} != "no" && !defined(RESCUE) LIBADD+= casper LIBADD+= cap_fileargs CFLAGS+= -DWITH_CASPER .endif HAS_TESTS= SUBDIR.${MK_TESTS}+= tests .include diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h index 3d8c12629682..d78aa37dc2ff 100644 --- a/usr.bin/tail/extern.h +++ b/usr.bin/tail/extern.h @@ -1,81 +1,81 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)extern.h 8.1 (Berkeley) 6/6/93 * * $FreeBSD$ */ #define WR(p, size) do { \ ssize_t res; \ res = write(STDOUT_FILENO, p, size); \ if (res != (ssize_t)size) { \ if (res == -1) \ oerr(); \ else \ errx(1, "stdout"); \ } \ } while (0) #define TAILMAPLEN (4<<20) struct mapinfo { off_t mapoff; off_t maxoff; size_t maplen; char *start; int fd; }; struct file_info { FILE *fp; const char *file_name; struct stat st; }; typedef struct file_info file_info_t; enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE }; void follow(file_info_t *, enum STYLE, off_t); void forward(FILE *, const char *, enum STYLE, off_t, struct stat *); void reverse(FILE *, const char *, enum STYLE, off_t, struct stat *); int bytes(FILE *, const char *, off_t); int lines(FILE *, const char *, off_t); void ierr(const char *); void oerr(void); int mapprint(struct mapinfo *, off_t, off_t); int maparound(struct mapinfo *, off_t); void printfn(const char *, int); -extern int Fflag, fflag, qflag, rflag, rval, no_files; +extern int Fflag, fflag, qflag, rflag, rval, no_files, vflag; extern fileargs_t *fa; diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c index 878cb5a4550b..3897d115effd 100644 --- a/usr.bin/tail/forward.c +++ b/usr.bin/tail/forward.c @@ -1,435 +1,435 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Edward Sze-Tyan Wang. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #ifndef lint static const char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93"; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "extern.h" static void rlines(FILE *, const char *fn, off_t, struct stat *); static int show(file_info_t *); static void set_events(file_info_t *files); /* defines for inner loop actions */ #define USE_SLEEP 0 #define USE_KQUEUE 1 #define ADD_EVENTS 2 static struct kevent *ev; static int action = USE_SLEEP; static int kq; static const file_info_t *last; /* * forward -- display the file, from an offset, forward. * * There are eight separate cases for this -- regular and non-regular * files, by bytes or lines and from the beginning or end of the file. * * FBYTES byte offset from the beginning of the file * REG seek * NOREG read, counting bytes * * FLINES line offset from the beginning of the file * REG read, counting lines * NOREG read, counting lines * * RBYTES byte offset from the end of the file * REG seek * NOREG cyclically read characters into a wrap-around buffer * * RLINES * REG mmap the file and step back until reach the correct offset. * NOREG cyclically read lines into a wrap-around array of buffers */ void forward(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp) { int ch; switch(style) { case FBYTES: if (off == 0) break; if (S_ISREG(sbp->st_mode)) { if (sbp->st_size < off) off = sbp->st_size; if (fseeko(fp, off, SEEK_SET) == -1) { ierr(fn); return; } } else while (off--) if ((ch = getc(fp)) == EOF) { if (ferror(fp)) { ierr(fn); return; } break; } break; case FLINES: if (off == 0) break; for (;;) { if ((ch = getc(fp)) == EOF) { if (ferror(fp)) { ierr(fn); return; } break; } if (ch == '\n' && !--off) break; } break; case RBYTES: if (S_ISREG(sbp->st_mode)) { if (sbp->st_size >= off && fseeko(fp, -off, SEEK_END) == -1) { ierr(fn); return; } } else if (off == 0) { while (getc(fp) != EOF); if (ferror(fp)) { ierr(fn); return; } } else if (bytes(fp, fn, off)) return; break; case RLINES: if (S_ISREG(sbp->st_mode)) if (!off) { if (fseeko(fp, (off_t)0, SEEK_END) == -1) { ierr(fn); return; } } else rlines(fp, fn, off, sbp); else if (off == 0) { while (getc(fp) != EOF); if (ferror(fp)) { ierr(fn); return; } } else if (lines(fp, fn, off)) return; break; default: break; } while ((ch = getc(fp)) != EOF) if (putchar(ch) == EOF) oerr(); if (ferror(fp)) { ierr(fn); return; } (void)fflush(stdout); } /* * rlines -- display the last offset lines of the file. */ static void rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp) { struct mapinfo map; off_t curoff, size; int i; if (!(size = sbp->st_size)) return; map.start = NULL; map.fd = fileno(fp); map.mapoff = map.maxoff = size; /* * Last char is special, ignore whether newline or not. Note that * size == 0 is dealt with above, and size == 1 sets curoff to -1. */ curoff = size - 2; while (curoff >= 0) { if (curoff < map.mapoff && maparound(&map, curoff) != 0) { ierr(fn); return; } for (i = curoff - map.mapoff; i >= 0; i--) if (map.start[i] == '\n' && --off == 0) break; /* `i' is either the map offset of a '\n', or -1. */ curoff = map.mapoff + i; if (i >= 0) break; } curoff++; if (mapprint(&map, curoff, size - curoff) != 0) { ierr(fn); exit(1); } /* Set the file pointer to reflect the length displayed. */ if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) { ierr(fn); return; } if (map.start != NULL && munmap(map.start, map.maplen)) { ierr(fn); return; } } static int show(file_info_t *file) { int ch; while ((ch = getc(file->fp)) != EOF) { - if (last != file && no_files > 1) { - if (!qflag) + if (last != file) { + if (vflag || (qflag == 0 && no_files > 1)) printfn(file->file_name, 1); last = file; } if (putchar(ch) == EOF) oerr(); } (void)fflush(stdout); if (ferror(file->fp)) { fclose(file->fp); file->fp = NULL; ierr(file->file_name); return 0; } clearerr(file->fp); return 1; } static void set_events(file_info_t *files) { int i, n = 0; file_info_t *file; struct timespec ts; struct statfs sf; ts.tv_sec = 0; ts.tv_nsec = 0; action = USE_KQUEUE; for (i = 0, file = files; i < no_files; i++, file++) { if (! file->fp) continue; if (fstatfs(fileno(file->fp), &sf) == 0 && (sf.f_flags & MNT_LOCAL) == 0) { action = USE_SLEEP; return; } if (Fflag && fileno(file->fp) != STDIN_FILENO) { EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_RENAME, 0, 0); n++; } EV_SET(&ev[n], fileno(file->fp), EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0); n++; } if (kevent(kq, ev, n, NULL, 0, &ts) < 0) { action = USE_SLEEP; } } /* * follow -- display the file, from an offset, forward. * */ void follow(file_info_t *files, enum STYLE style, off_t off) { int active, ev_change, i, n = -1; struct stat sb2; file_info_t *file; FILE *ftmp; struct timespec ts; /* Position each of the files */ file = files; active = 0; n = 0; for (i = 0; i < no_files; i++, file++) { if (file->fp) { active = 1; n++; - if (no_files > 1 && !qflag) + if (vflag || (qflag == 0 && no_files > 1)) printfn(file->file_name, 1); forward(file->fp, file->file_name, style, off, &file->st); if (Fflag && fileno(file->fp) != STDIN_FILENO) n++; } } if (!Fflag && !active) return; last = --file; kq = kqueue(); if (kq < 0) err(1, "kqueue"); ev = malloc(n * sizeof(struct kevent)); if (! ev) err(1, "Couldn't allocate memory for kevents."); set_events(files); for (;;) { ev_change = 0; if (Fflag) { for (i = 0, file = files; i < no_files; i++, file++) { if (!file->fp) { file->fp = fileargs_fopen(fa, file->file_name, "r"); if (file->fp != NULL && fstat(fileno(file->fp), &file->st) == -1) { fclose(file->fp); file->fp = NULL; } if (file->fp != NULL) ev_change++; continue; } if (fileno(file->fp) == STDIN_FILENO) continue; ftmp = fileargs_fopen(fa, file->file_name, "r"); if (ftmp == NULL || fstat(fileno(ftmp), &sb2) == -1) { if (errno != ENOENT) ierr(file->file_name); show(file); if (file->fp != NULL) { fclose(file->fp); file->fp = NULL; } if (ftmp != NULL) { fclose(ftmp); } ev_change++; continue; } if (sb2.st_ino != file->st.st_ino || sb2.st_dev != file->st.st_dev || sb2.st_nlink == 0) { show(file); fclose(file->fp); file->fp = ftmp; memcpy(&file->st, &sb2, sizeof(struct stat)); ev_change++; } else { fclose(ftmp); } } } for (i = 0, file = files; i < no_files; i++, file++) if (file->fp && !show(file)) ev_change++; if (ev_change) set_events(files); switch (action) { case USE_KQUEUE: ts.tv_sec = 1; ts.tv_nsec = 0; /* * In the -F case we set a timeout to ensure that * we re-stat the file at least once every second. */ n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL); if (n < 0) err(1, "kevent"); if (n == 0) { /* timeout */ break; } else if (ev->filter == EVFILT_READ && ev->data < 0) { /* file shrank, reposition to end */ if (lseek(ev->ident, (off_t)0, SEEK_END) == -1) { ierr(file->file_name); continue; } } break; case USE_SLEEP: (void) usleep(250000); break; } } } diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1 index 771d72c56989..59b632868dbd 100644 --- a/usr.bin/tail/tail.1 +++ b/usr.bin/tail/tail.1 @@ -1,202 +1,213 @@ .\" Copyright (c) 1980, 1990, 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" This code is derived from software contributed to Berkeley by .\" the Institute of Electrical and Electronics Engineers, Inc. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" @(#)tail.1 8.1 (Berkeley) 6/6/93 .\" $FreeBSD$ .\" -.Dd March 22, 2020 +.Dd July 12, 2022 .Dt TAIL 1 .Os .Sh NAME .Nm tail .Nd display the last part of a file .Sh SYNOPSIS .Nm .Op Fl F | f | r -.Op Fl q +.Op Fl qv .Oo .Fl b Ar number | Fl c Ar number | Fl n Ar number .Oc .Op Ar .Sh DESCRIPTION The .Nm utility displays the contents of .Ar file or, by default, its standard input, to the standard output. .Pp The display begins at a byte, line or 512-byte block location in the input. Numbers having a leading plus .Pq Ql + sign are relative to the beginning of the input, for example, .Dq Li "-c +2" starts the display at the second byte of the input. Numbers having a leading minus .Pq Ql - sign or no explicit sign are relative to the end of the input, for example, .Dq Li "-n 2" displays the last two lines of the input. The default starting location is .Dq Li "-n 10" , or the last 10 lines of the input. .Pp The options are as follows: .Bl -tag -width indent .It Fl b Ar number , Fl -blocks Ns = Ns Ar number The location is .Ar number 512-byte blocks. .It Fl c Ar number , Fl -bytes Ns = Ns Ar number The location is .Ar number bytes. .It Fl f The .Fl f option causes .Nm to not stop when end of file is reached, but rather to wait for additional data to be appended to the input. The .Fl f option is ignored if the standard input is a pipe, but not if it is a FIFO. .It Fl F The .Fl F option implies the .Fl f option, but .Nm will also check to see if the file being followed has been renamed or rotated. The file is closed and reopened when .Nm detects that the filename being read from has a new inode number. .Pp If the file being followed does not (yet) exist or if it is removed, tail will keep looking and will display the file from the beginning if and when it is created. .Pp The .Fl F option is the same as the .Fl f option if reading from standard input rather than a file. .It Fl n Ar number , Fl -lines Ns = Ns Ar number The location is .Ar number lines. -.It Fl q +.It Fl q, Fl -quiet, Fl -silent Suppresses printing of headers when multiple files are being examined. .It Fl r The .Fl r option causes the input to be displayed in reverse order, by line. Additionally, this option changes the meaning of the .Fl b , c and .Fl n options. When the .Fl r option is specified, these options specify the number of bytes, lines or 512-byte blocks to display, instead of the bytes, lines or blocks from the beginning or end of the input from which to begin the display. The default for the .Fl r option is to display all of the input. +.It Fl v, Fl -verbose +Prepend each file with a header. .El .Pp -If more than a single file is specified, each file is preceded by a +If more than a single file is specified, or if the +.Fl v +option is used, each file is preceded by a header consisting of the string .Dq Li "==> " Ns Ar XXX Ns Li " <==" where .Ar XXX -is the name of the file unless +is the name of the file. +The .Fl q -flag is specified. +flag disables the printing of the header in all cases. +.Pp +All +.Ar number +arguments may also be specified with size suffixes supported by +.Xr expand_number 3 . .Sh EXIT STATUS .Ex -std .Sh EXAMPLES To display the last 500 lines of the file .Ar foo : .Pp .Dl $ tail -n 500 foo .Pp Keep .Pa /var/log/messages open, displaying to the standard output anything appended to the file: .Pp .Dl $ tail -F /var/log/messages .Sh SEE ALSO .Xr cat 1 , .Xr head 1 , -.Xr sed 1 +.Xr sed 1 , +.Xr expand_number 3 .Sh STANDARDS The .Nm utility is expected to be a superset of the .St -p1003.2-92 specification. In particular, the .Fl F , .Fl b and .Fl r options are extensions to that standard. .Pp The historic command line syntax of .Nm is supported by this implementation. The only difference between this implementation and historic versions of .Nm , once the command line syntax translation has been done, is that the .Fl b , .Fl c and .Fl n options modify the .Fl r option, i.e., .Dq Li "-r -c 4" displays the last 4 characters of the last line of the input, while the historic tail (using the historic syntax .Dq Li -4cr ) would ignore the .Fl c option and display the last 4 lines of the input. .Sh HISTORY A .Nm command appeared in PWB UNIX. diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c index 874557f105ec..51598d451bed 100644 --- a/usr.bin/tail/tail.c +++ b/usr.bin/tail/tail.c @@ -1,370 +1,380 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Edward Sze-Tyan Wang. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1991, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif #ifndef lint static const char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93"; #endif #include #include #include #include #include #include #include #include #include #include #include +#include + #include #include #include "extern.h" -int Fflag, fflag, qflag, rflag, rval, no_files; +int Fflag, fflag, qflag, rflag, rval, no_files, vflag; fileargs_t *fa; static void obsolete(char **); static void usage(void); static const struct option long_opts[] = { {"blocks", required_argument, NULL, 'b'}, {"bytes", required_argument, NULL, 'c'}, {"lines", required_argument, NULL, 'n'}, + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 'q'}, + {"verbose", no_argument, NULL, 'v'}, {NULL, no_argument, NULL, 0} }; int main(int argc, char *argv[]) { struct stat sb; const char *fn; FILE *fp; off_t off; enum STYLE style; int ch, first; file_info_t file, *filep, *files; - char *p; cap_rights_t rights; /* * Tail's options are weird. First, -n10 is the same as -n-10, not * -n+10. Second, the number options are 1 based and not offsets, * so -n+1 is the first line, and -c-1 is the last byte. Third, the * number options for the -r option specify the number of things that * get displayed, not the starting point in the file. The one major * incompatibility in this version as compared to historical versions * is that the 'r' option couldn't be modified by the -lbc options, * i.e. it was always done in lines. This version treats -rc as a * number of characters in reverse order. Finally, the default for * -r is the entire file, not 10 lines. */ #define ARG(units, forward, backward) { \ if (style) \ usage(); \ - off = strtoll(optarg, &p, 10) * (units); \ - if (*p) \ + if (expand_number(optarg, &off)) \ + err(1, "illegal offset -- %s", optarg); \ + if (off > INT64_MAX / units || off < INT64_MIN / units ) \ errx(1, "illegal offset -- %s", optarg); \ switch(optarg[0]) { \ case '+': \ if (off) \ off -= (units); \ style = (forward); \ break; \ case '-': \ off = -off; \ /* FALLTHROUGH */ \ default: \ style = (backward); \ break; \ } \ } obsolete(argv); style = NOTSET; off = 0; - while ((ch = getopt_long(argc, argv, "+Fb:c:fn:qr", long_opts, NULL)) != + while ((ch = getopt_long(argc, argv, "+Fb:c:fn:qrv", long_opts, NULL)) != -1) switch(ch) { case 'F': /* -F is superset of (and implies) -f */ Fflag = fflag = 1; break; case 'b': ARG(512, FBYTES, RBYTES); break; case 'c': ARG(1, FBYTES, RBYTES); break; case 'f': fflag = 1; break; case 'n': ARG(1, FLINES, RLINES); break; case 'q': qflag = 1; + vflag = 0; break; case 'r': rflag = 1; break; + case 'v': + vflag = 1; + qflag = 0; + break; case '?': default: usage(); } argc -= optind; argv += optind; no_files = argc ? argc : 1; cap_rights_init(&rights, CAP_FSTAT, CAP_FSTATFS, CAP_FCNTL, CAP_MMAP_R); if (fflag) cap_rights_set(&rights, CAP_EVENT); if (caph_rights_limit(STDIN_FILENO, &rights) < 0 || caph_limit_stderr() < 0 || caph_limit_stdout() < 0) err(1, "can't limit stdio rights"); fa = fileargs_init(argc, argv, O_RDONLY, 0, &rights, FA_OPEN); if (fa == NULL) err(1, "unable to init casper"); caph_cache_catpages(); if (caph_enter_casper() < 0) err(1, "unable to enter capability mode"); /* * If displaying in reverse, don't permit follow option, and convert * style values. */ if (rflag) { if (fflag) usage(); if (style == FBYTES) style = RBYTES; else if (style == FLINES) style = RLINES; } /* * If style not specified, the default is the whole file for -r, and * the last 10 lines if not -r. */ if (style == NOTSET) { if (rflag) { off = 0; style = REVERSE; } else { off = 10; style = RLINES; } } if (*argv && fflag) { files = malloc(no_files * sizeof(struct file_info)); if (files == NULL) err(1, "Couldn't malloc space for file descriptors."); for (filep = files; (fn = *argv++); filep++) { filep->file_name = fn; filep->fp = fileargs_fopen(fa, filep->file_name, "r"); if (filep->fp == NULL || fstat(fileno(filep->fp), &filep->st)) { if (filep->fp != NULL) { fclose(filep->fp); filep->fp = NULL; } if (!Fflag || errno != ENOENT) ierr(filep->file_name); } } follow(files, style, off); free(files); } else if (*argv) { for (first = 1; (fn = *argv++);) { if ((fp = fileargs_fopen(fa, fn, "r")) == NULL || fstat(fileno(fp), &sb)) { ierr(fn); continue; } - if (argc > 1 && !qflag) { + if (vflag || (qflag == 0 && argc > 1)) { printfn(fn, !first); first = 0; } if (rflag) reverse(fp, fn, style, off, &sb); else forward(fp, fn, style, off, &sb); } } else { fn = "stdin"; if (fstat(fileno(stdin), &sb)) { ierr(fn); exit(1); } /* * Determine if input is a pipe. 4.4BSD will set the SOCKET * bit in the st_mode field for pipes. Fix this then. */ if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 && errno == ESPIPE) { errno = 0; fflag = 0; /* POSIX.2 requires this. */ } if (rflag) { reverse(stdin, fn, style, off, &sb); } else if (fflag) { file.file_name = fn; file.fp = stdin; follow(&file, style, off); } else { forward(stdin, fn, style, off, &sb); } } fileargs_free(fa); exit(rval); } /* * Convert the obsolete argument form into something that getopt can handle. * This means that anything of the form [+-][0-9][0-9]*[lbc][Ffr] that isn't * the option argument for a -b, -c or -n option gets converted. */ static void obsolete(char *argv[]) { char *ap, *p, *t; size_t len; char *start; while ((ap = *++argv)) { /* Return if "--" or not an option of any form. */ if (ap[0] != '-') { if (ap[0] != '+') return; } else if (ap[1] == '-') return; switch(*++ap) { /* Old-style option. */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* Malloc space for dash, new option and argument. */ len = strlen(*argv); if ((start = p = malloc(len + 3)) == NULL) err(1, "malloc"); *p++ = '-'; /* * Go to the end of the option argument. Save off any * trailing options (-3lf) and translate any trailing * output style characters. */ t = *argv + len - 1; if (*t == 'F' || *t == 'f' || *t == 'r') { *p++ = *t; *t-- = '\0'; } switch(*t) { case 'b': *p++ = 'b'; *t = '\0'; break; case 'c': *p++ = 'c'; *t = '\0'; break; case 'l': *t = '\0'; /* FALLTHROUGH */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *p++ = 'n'; break; default: errx(1, "illegal option -- %s", *argv); } *p++ = *argv[0]; (void)strcpy(p, ap); *argv = start; continue; /* * Options w/ arguments, skip the argument and continue * with the next option. */ case 'b': case 'c': case 'n': if (!ap[1]) ++argv; /* FALLTHROUGH */ /* Options w/o arguments, continue with the next option. */ case 'F': case 'f': case 'r': continue; /* Illegal option, return and let getopt handle it. */ default: return; } } } static void usage(void) { (void)fprintf(stderr, "usage: tail [-F | -f | -r] [-q] [-b # | -c # | -n #]" " [file ...]\n"); exit(1); } diff --git a/usr.bin/tail/tests/tail_test.sh b/usr.bin/tail/tests/tail_test.sh index 66d435f2cd02..e8263864f28c 100755 --- a/usr.bin/tail/tests/tail_test.sh +++ b/usr.bin/tail/tests/tail_test.sh @@ -1,375 +1,419 @@ # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # # Copyright (c) 2016 Alan Somers # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $FreeBSD$ atf_test_case empty_r empty_r_head() { atf_set "descr" "Reverse an empty file" } empty_r_body() { touch infile expectfile tail -r infile > outfile tail -r < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case file_r file_r_head() { atf_set "descr" "Reverse a file" } file_r_body() { cat > infile < expectfile << HERE This is the third line This is the second line This is the first line HERE tail -r infile > outfile tail -r < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case file_rn2 file_rn2_head() { atf_set "descr" "Reverse the last two lines of a file" } file_rn2_body() { cat > infile < expectfile << HERE This is the third line This is the second line HERE tail -rn2 infile > outfile tail -rn2 < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } # Regression test for PR 222671 # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=222671 atf_test_case pipe_leading_newline_r pipe_leading_newline_r_head() { atf_set "descr" "Reverse a pipe whose first character is a newline" } pipe_leading_newline_r_body() { cat > expectfile << HERE 3 2 1 HERE printf '\n1\n2\n3\n' | tail -r > outfile printf '\n1\n2\n3\n' | tail -r > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case file_rc28 file_rc28_head() { atf_set "descr" "Reverse a file and display the last 28 characters" } file_rc28_body() { cat > infile < expectfile << HERE This is the third line line HERE tail -rc28 infile > outfile tail -rc28 < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case file_rc28 file_rc28_head() { atf_set "descr" "Reverse a file and display the last 28 characters" } file_rc28_body() { cat > infile < expectfile << HERE This is the third line line HERE tail -rc28 infile > outfile tail -rc28 < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case longfile_r longfile_r_head() { atf_set "descr" "Reverse a long file" } longfile_r_body() { jot -w "%0511d" 1030 0 > infile jot -w "%0511d" 1030 1029 0 -1 > expectfile tail -r infile > outfile tail -r < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case longfile_r_enomem longfile_r_enomem_head() { atf_set "descr" "Reverse a file that's too long to store in RAM" } longfile_r_enomem_body() { # When we reverse a file that's too long for RAM, tail should drop the # first part and just print what it can. We'll check that the last # part is ok { ulimit -v 32768 || atf_skip "Can't adjust ulimit" jot -w "%01023d" 32768 0 | tail -r > outfile ; } if [ "$?" -ne 1 ]; then atf_skip "Didn't get ENOMEM. Adjust test parameters" fi # We don't know how much of the input we dropped. So just check that # the first ten lines of tail's output are the same as the last ten of # the input jot -w "%01023d" 10 32767 0 -1 > expectfile head -n 10 outfile > outtrunc diff expectfile outtrunc atf_check cmp expectfile outtrunc } atf_test_case longfile_r_longlines longfile_r_longlines_head() { atf_set "descr" "Reverse a long file with extremely long lines" } longfile_r_longlines_body() { jot -s " " -w "%07d" 18000 0 > infile jot -s " " -w "%07d" 18000 18000 >> infile jot -s " " -w "%07d" 18000 36000 >> infile jot -s " " -w "%07d" 18000 36000 > expectfile jot -s " " -w "%07d" 18000 18000 >> expectfile jot -s " " -w "%07d" 18000 0 >> expectfile tail -r infile > outfile tail -r < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case longfile_rc135782 longfile_rc135782_head() { atf_set "descr" "Reverse a long file and print the last 135,782 bytes" } longfile_rc135782_body() { jot -w "%063d" 9000 0 > infile jot -w "%063d" 2121 8999 0 -1 > expectfile echo "0000000000000000000000000000000006878" >> expectfile tail -rc135782 infile > outfile tail -rc135782 < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case longfile_rc145782_longlines longfile_rc145782_longlines_head() { atf_set "descr" "Reverse a long file with extremely long lines and print the last 145,782 bytes" } longfile_rc145782_longlines_body() { jot -s " " -w "%07d" 18000 0 > infile jot -s " " -w "%07d" 18000 18000 >> infile jot -s " " -w "%07d" 18000 36000 >> infile jot -s " " -w "%07d" 18000 36000 > expectfile echo -n "35777 " >> expectfile jot -s " " -w "%07d" 222 35778 >> expectfile tail -rc145782 infile > outfile tail -rc145782 < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case longfile_rn2500 longfile_rn2500_head() { atf_set "descr" "Reverse a long file and print the last 2,500 lines" } longfile_rn2500_body() { jot -w "%063d" 9000 0 > infile jot -w "%063d" 2500 8999 0 -1 > expectfile tail -rn2500 infile > outfile tail -rn2500 < infile > outpipe atf_check cmp expectfile outfile atf_check cmp expectfile outpipe } atf_test_case broken_pipe broken_pipe_head() { atf_set "descr" "Do not print bogus errno based output on short writes" } broken_pipe_body() { atf_check -o save:ints seq -f '%128g' 1 1000 atf_check -s ignore \ -e "inline:tail: stdout\nexit code: 1\n" \ -x '(tail -n 856 ints; echo exit code: $? >&2) | sleep 2' } atf_test_case stdin stdin_head() { atf_set "descr" "Check basic operations on standard input" } stdin_body() { seq 1 5 > infile seq 1 5 > expectfile seq 5 1 > expectfile_r tail < infile > outfile tail -r < infile > outfile_r atf_check cmp expectfile outfile atf_check cmp expectfile_r outfile_r } atf_test_case follow follow_head() { atf_set "descr" "Basic regression test for -f" } follow_body() { local pid seq 1 5 > expectfile seq 1 3 > infile tail -f infile > outfile & pid=$! sleep 0.1 seq 4 5 >> infile sleep 0.1 atf_check cmp expectfile outfile atf_check kill $pid } atf_test_case follow_stdin follow_stdin_head() { atf_set "descr" "Verify that -f works with files piped to standard input" } follow_stdin_body() { local pid seq 1 5 > expectfile seq 1 3 > infile tail -f < infile > outfile & pid=$! sleep 0.1 seq 4 5 >> infile sleep 0.1 atf_check cmp expectfile outfile atf_check kill $pid } atf_test_case follow_rename follow_rename_head() { atf_set "descr" "Verify that -F works" } follow_rename_body() { local pid seq 1 5 > expectfile seq 1 3 > infile tail -F infile > outfile & pid=$! seq 4 5 > infile_new atf_check mv infile infile_old atf_check mv infile_new infile # tail -F polls for a new file every 1s. sleep 2 atf_check cmp expectfile outfile atf_check kill $pid } +atf_test_case silent_header +silent_header_head() { + atf_set "descr" "Test tail(1)'s silent header feature" +} +silent_header_body() { + jot 11 1 11 > file1 + jot 11 2 12 > file2 + jot 10 2 11 > expectfile + jot 10 3 12 >> expectfile + tail -q file1 file2 > outfile + atf_check cmp outfile expectfile +} + +atf_test_case verbose_header +verbose_header_head() { + atf_set "descr" "Test tail(1)'s verbose header feature" +} +verbose_header_body() { + jot 11 1 11 > file1 + echo '==> file1 <==' > expectfile + jot 10 2 11 >> expectfile + tail -v file1 > outfile + atf_check cmp outfile expectfile +} + +atf_test_case si_number +si_number_head() { + atf_set "descr" "Test tail(1)'s SI number feature" +} +si_number_body() { + jot -b aaaaaaa 129 > file1 + jot -b aaaaaaa 128 > expectfile + tail -c 1k file1 > outfile + atf_check cmp outfile expectfile + jot 1025 1 1025 > file1 + jot 1024 2 1025 > expectfile + tail -n 1k file1 > outfile + atf_check cmp outfile expectfile +} + + atf_init_test_cases() { atf_add_test_case empty_r atf_add_test_case file_r atf_add_test_case file_rc28 atf_add_test_case file_rn2 atf_add_test_case pipe_leading_newline_r # The longfile tests are designed to exercise behavior in r_buf(), # which operates on 128KB blocks atf_add_test_case longfile_r atf_add_test_case longfile_r_enomem atf_add_test_case longfile_r_longlines atf_add_test_case longfile_rc135782 atf_add_test_case longfile_rc145782_longlines atf_add_test_case longfile_rn2500 atf_add_test_case broken_pipe atf_add_test_case stdin atf_add_test_case follow atf_add_test_case follow_stdin atf_add_test_case follow_rename + atf_add_test_case silent_header + atf_add_test_case verbose_header + atf_add_test_case si_number }