# # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy is of the CDDL is also available via the Internet # at http://www.illumos.org/license/CDDL. # # # Copyright 2010 Chris Love. All rights reserved. # Copyright (c) 2018, Joyent, Inc. # PROG= tail XPG4PROG= $(PROG) OBJS= forward.o misc.o read.o reverse.o tail.o SRCS= $(OBJS:%.o=%.c) include ../Makefile.cmd include ../Makefile.essbin CLOBBERFILES= $(PROG) CPPFLAGS += -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 CSTD= $(CSTD_GNU99) SMOFF += leaks # install rules $(ROOTINC)/% : % $(INS.file) .KEEP_STATE: .PARALLEL: $(OBJS) all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) install: all .WAIT $(ROOTPROG) $(ROOTXPG4PROG) $(ROOTUSRBINLINKS) $(ROOTXPG4PROG): -$(RM) $@ -$(LN) -s ../../bin/$(PROG) $@ clean: $(RM) $(OBJS) include ../Makefile.targ 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. 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. 4. 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. Copyright (c) 1988, 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. 4. 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 UTILITY /* * 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. * 4. 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 _TAIL_EXTERN_H #define _TAIL_EXTERN_H #include #include #include #define WR(p, size) do { \ if (write(STDOUT_FILENO, p, size) != (ssize_t)size) \ oerr(); \ } 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; char *file_name; struct stat st; file_obj_t fobj[2]; }; 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); extern int Fflag, fflag, qflag, rflag, rval, no_files; #endif /* _TAIL_EXTERN_H */ /* * 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. * 4. 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. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ #include #include #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_PORT 1 #define ADD_EVENTS 2 int port; int action = USE_PORT; 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) == 0) 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) (void) printf("\n==> %s <==\n", file->file_name); last = file; } if (putchar(ch) == EOF) oerr(); } (void) fflush(stdout); if (ferror(file->fp)) { (void) fclose(file->fp); file->fp = NULL; ierr(file->file_name); return (0); } clearerr(file->fp); return (1); } static void associate(file_info_t *file, boolean_t assoc, port_event_t *ev) { char buf[64], *name; int i; if (action != USE_PORT || file->fp == NULL) return; if (!S_ISREG(file->st.st_mode)) { /* * For FIFOs, we use PORT_SOURCE_FD as our port event source. */ if (assoc) { (void) port_associate(port, PORT_SOURCE_FD, fileno(file->fp), POLLIN, file); } else { (void) port_dissociate(port, PORT_SOURCE_FD, fileno(file->fp)); } return; } bzero(&file->fobj, sizeof (file->fobj)); if (!Fflag) { /* * PORT_SOURCE_FILE only allows us to specify a file name, not * a file descriptor. If we are following a specific file (as * opposed to a file name) and we were to specify the name of * the file to port_associate() and that file were moved * aside, we would not be able to reassociate an event because * we would not know a name that would resolve to the new file * (indeed, there might not be such a name -- the file may * have been unlinked). But there _is_ a name that we know * maps to the file and doesn't change: the name of the * representation of the open file descriptor in /proc. We * therefore associate with this name (and the underlying * file), not the name of the file as specified at the command * line. This also has the (desirable) side-effect of * insulating against FILE_RENAME_FROM and FILE_RENAME_TO * events that we need to ignore to assure that we don't lose * FILE_TRUNC events. */ (void) snprintf(buf, sizeof (buf), "/proc/self/fd/%d", fileno(file->fp)); name = buf; } else { name = file->file_name; } /* * Note that portfs uses the address of the specified file_obj_t to * tag an association; if one creates a different association with a * (different) file_obj_t that happens to be at the same address, * the first association will be implicitly removed. To assure that * each association has a disjoint file_obj_t, we allocate the memory * for each in the file_info, not on the stack. */ file->fobj[0].fo_name = name; file->fobj[1].fo_name = name; if (assoc) { /* * To assure that we cannot possibly drop a FILE_TRUNC event, * we have two different PORT_SOURCE_FILE associations with the * port: one to get only FILE_MODIFIED events and another to * get only FILE_TRUNC events. This assures that we always * have an active association for FILE_TRUNC events when the * seek offset is non-zero. Note that the association order * _must_ be FILE_TRUNC followed by FILE_MODIFIED: if a single * event induces both a FILE_TRUNC and a FILE_MODIFIED (as * a VE_CREATE vnode event does), we must process the * FILE_TRUNC before FILE_MODIFIED -- and the order in which * these are processed will be the association order. So * if we see a FILE_TRUNC, we dissociate/reassociate the * FILE_MODIFIED association. */ if (ev == NULL || (ev->portev_events & FILE_TRUNC) || !(ev->portev_events & (FILE_MODIFIED | FILE_TRUNC))) { (void) port_associate(port, PORT_SOURCE_FILE, (uintptr_t)&file->fobj[0], FILE_TRUNC, file); (void) port_dissociate(port, PORT_SOURCE_FILE, (uintptr_t)&file->fobj[1]); ev = NULL; } if (ev == NULL || (ev->portev_events & FILE_MODIFIED) || !(ev->portev_events & (FILE_MODIFIED | FILE_TRUNC))) { (void) port_associate(port, PORT_SOURCE_FILE, (uintptr_t)&file->fobj[1], FILE_MODIFIED, file); } } else { for (i = 0; i <= 1; i++) { (void) port_dissociate(port, PORT_SOURCE_FILE, (uintptr_t)&file->fobj[i]); } } } static void set_events(file_info_t *files) { int i; file_info_t *file; for (i = 0, file = files; i < no_files; i++, file++) { if (! file->fp) continue; (void) fstat(fileno(file->fp), &file->st); associate(file, B_TRUE, NULL); } } /* * 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; struct timespec ts; port_event_t ev; /* 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) (void) printf("\n==> %s <==\n", file->file_name); forward(file->fp, file->file_name, style, off, &file->st); if (Fflag && fileno(file->fp) != STDIN_FILENO) n++; } } if (!Fflag && !active) return; last = --file; if (action == USE_PORT && (stat("/proc/self/fd", &sb2) == -1 || !S_ISDIR(sb2.st_mode) || (port = port_create()) == -1)) action = USE_SLEEP; set_events(files); for (;;) { ev_change = 0; if (Fflag) { for (i = 0, file = files; i < no_files; i++, file++) { if (!file->fp) { file->fp = fopen(file->file_name, "r"); if (file->fp != NULL && fstat(fileno(file->fp), &file->st) == -1) { (void) fclose(file->fp); file->fp = NULL; } if (file->fp != NULL) ev_change++; continue; } if (fileno(file->fp) == STDIN_FILENO) continue; if (stat(file->file_name, &sb2) == -1) { if (errno != ENOENT) ierr(file->file_name); (void) show(file); (void) fclose(file->fp); file->fp = NULL; ev_change++; continue; } if (sb2.st_ino != file->st.st_ino || sb2.st_dev != file->st.st_dev || sb2.st_nlink == 0) { (void) show(file); associate(file, B_FALSE, NULL); file->fp = freopen(file->file_name, "r", file->fp); if (file->fp != NULL) { (void) memcpy(&file->st, &sb2, sizeof (struct stat)); } else if (errno != ENOENT) ierr(file->file_name); ev_change++; } } } 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_PORT: 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 = port_get(port, &ev, Fflag ? &ts : NULL); if (n == 0) { file = (file_info_t *)ev.portev_user; associate(file, B_TRUE, &ev); if (ev.portev_events & FILE_TRUNC) (void) fseek(file->fp, 0, SEEK_SET); } break; case USE_SLEEP: (void) usleep(250000); break; } } } /* * 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. * 4. 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 #include #include #include #include #include #include #include #include #include "extern.h" void ierr(const char *fname) { warn("%s", fname); rval = 1; } void oerr(void) { err(1, "stdout"); } /* * Print `len' bytes from the file associated with `mip', starting at * absolute file offset `startoff'. May move map window. */ int mapprint(struct mapinfo *mip, off_t startoff, off_t len) { int n; while (len > 0) { if (startoff < mip->mapoff || startoff >= mip->mapoff + (off_t)mip->maplen) { if (maparound(mip, startoff) != 0) return (1); } n = (mip->mapoff + mip->maplen) - startoff; if (n > len) n = len; WR(mip->start + (startoff - mip->mapoff), n); startoff += n; len -= n; } return (0); } /* * Move the map window so that it contains the byte at absolute file * offset `offset'. The start of the map window will be TAILMAPLEN * aligned. */ int maparound(struct mapinfo *mip, off_t offset) { if (mip->start != NULL && munmap(mip->start, mip->maplen) != 0) return (1); mip->mapoff = offset & ~((off_t)TAILMAPLEN - 1); mip->maplen = TAILMAPLEN; if ((off_t)mip->maplen > mip->maxoff - mip->mapoff) mip->maplen = mip->maxoff - mip->mapoff; if (mip->maplen == 0) abort(); if ((mip->start = mmap(NULL, mip->maplen, PROT_READ, MAP_SHARED, mip->fd, mip->mapoff)) == MAP_FAILED) return (1); return (0); } /* * 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. * 4. 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 #include #include #include #include #include #include #include #include #include #include "extern.h" /* * bytes -- read bytes to an offset from the end and display. * * This is the function that reads to a byte offset from the end of the input, * storing the data in a wrap-around buffer which is then displayed. If the * rflag is set, the data is displayed in lines in reverse order, and this * routine has the usual nastiness of trying to find the newlines. Otherwise, * it is displayed from the character closest to the beginning of the input to * the end. */ int bytes(FILE *fp, const char *fn, off_t off) { int ch, len, tlen; char *ep, *p, *t; int wrap; char *sp; if ((sp = p = malloc(off)) == NULL) err(1, "malloc"); for (wrap = 0, ep = p + off; (ch = getc(fp)) != EOF; ) { *p = ch; if (++p == ep) { wrap = 1; p = sp; } } if (ferror(fp)) { ierr(fn); free(sp); return (1); } if (rflag) { for (t = p - 1, len = 0; t >= sp; --t, ++len) if (*t == '\n' && len) { WR(t + 1, len); len = 0; } if (wrap) { tlen = len; for (t = ep - 1, len = 0; t >= p; --t, ++len) if (*t == '\n') { if (len) { WR(t + 1, len); len = 0; } if (tlen) { WR(sp, tlen); tlen = 0; } } if (len) WR(t + 1, len); if (tlen) WR(sp, tlen); } } else { if (wrap && (len = ep - p)) WR(p, len); len = p - sp; if (len) WR(sp, len); } free(sp); return (0); } /* * lines -- read lines to an offset from the end and display. * * This is the function that reads to a line offset from the end of the input, * storing the data in an array of buffers which is then displayed. If the * rflag is set, the data is displayed in lines in reverse order, and this * routine has the usual nastiness of trying to find the newlines. Otherwise, * it is displayed from the line closest to the beginning of the input to * the end. */ int lines(FILE *fp, const char *fn, off_t off) { struct { int blen; uint_t len; char *l; } *llines; int ch, rc; char *p, *sp; int blen, cnt, recno, wrap; if ((llines = malloc(off * sizeof (*llines))) == NULL) err(1, "malloc"); bzero(llines, off * sizeof (*llines)); p = sp = NULL; blen = cnt = recno = wrap = 0; rc = 0; while ((ch = getc(fp)) != EOF) { if (++cnt > blen) { if ((sp = realloc(sp, blen += 1024)) == NULL) err(1, "realloc"); p = sp + cnt - 1; } *p++ = ch; if (ch == '\n') { if ((int)llines[recno].blen < cnt) { llines[recno].blen = cnt + 256; if ((llines[recno].l = realloc(llines[recno].l, llines[recno].blen)) == NULL) err(1, "realloc"); } bcopy(sp, llines[recno].l, llines[recno].len = cnt); cnt = 0; p = sp; if (++recno == off) { wrap = 1; recno = 0; } } } if (ferror(fp)) { ierr(fn); rc = 1; goto done; } if (cnt) { llines[recno].l = sp; sp = NULL; llines[recno].len = cnt; if (++recno == off) { wrap = 1; recno = 0; } } if (rflag) { for (cnt = recno - 1; cnt >= 0; --cnt) WR(llines[cnt].l, llines[cnt].len); if (wrap) for (cnt = off - 1; cnt >= recno; --cnt) WR(llines[cnt].l, llines[cnt].len); } else { if (wrap) for (cnt = recno; cnt < off; ++cnt) WR(llines[cnt].l, llines[cnt].len); for (cnt = 0; cnt < recno; ++cnt) WR(llines[cnt].l, llines[cnt].len); } done: for (cnt = 0; cnt < off; cnt++) free(llines[cnt].l); free(sp); free(llines); return (rc); } /* * 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. * 4. 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 #include #include #include #include #include #include #include #include #include #include #include "extern.h" static void r_buf(FILE *, const char *); static void r_reg(FILE *, const char *, enum STYLE, off_t, struct stat *); /* * reverse -- display input in reverse order by line. * * There are six separate cases for this -- regular and non-regular * files by bytes, lines or the whole file. * * BYTES display N bytes * REG mmap the file and display the lines * NOREG cyclically read characters into a wrap-around buffer * * LINES display N lines * REG mmap the file and display the lines * NOREG cyclically read lines into a wrap-around array of buffers * * FILE display the entire file * REG mmap the file and display the lines * NOREG cyclically read input into a linked list of buffers */ void reverse(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp) { if (style != REVERSE && off == 0) return; if (S_ISREG(sbp->st_mode)) r_reg(fp, fn, style, off, sbp); else switch (style) { case FBYTES: case RBYTES: (void) bytes(fp, fn, off); break; case FLINES: case RLINES: (void) lines(fp, fn, off); break; case REVERSE: r_buf(fp, fn); break; default: break; } } /* * r_reg -- display a regular file in reverse order by line. */ static void r_reg(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp) { struct mapinfo map; off_t curoff, size, lineend; int i; if ((size = sbp->st_size) == 0) return; map.start = NULL; map.mapoff = map.maxoff = size; map.fd = fileno(fp); /* * 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; lineend = size; while (curoff >= 0) { if (curoff < map.mapoff || curoff >= map.mapoff + (off_t)map.maplen) { if (maparound(&map, curoff) != 0) { ierr(fn); return; } } for (i = curoff - map.mapoff; i >= 0; i--) { if (style == RBYTES && --off == 0) break; if (map.start[i] == '\n') break; } /* `i' is either the map offset of a '\n', or -1. */ curoff = map.mapoff + i; if (i < 0) continue; /* Print the line and update offsets. */ if (mapprint(&map, curoff + 1, lineend - curoff - 1) != 0) { ierr(fn); return; } lineend = curoff + 1; curoff--; if (style == RLINES) off--; if (off == 0 && style != REVERSE) { /* Avoid printing anything below. */ curoff = 0; break; } } if (curoff < 0 && mapprint(&map, 0, lineend) != 0) { ierr(fn); return; } if (map.start != NULL && munmap(map.start, map.maplen)) ierr(fn); } typedef struct bf { struct bf *next; struct bf *prev; int len; char *l; } BF; /* * r_buf -- display a non-regular file in reverse order by line. * * This is the function that saves the entire input, storing the data in a * doubly linked list of buffers and then displays them in reverse order. * It has the usual nastiness of trying to find the newlines, as there's no * guarantee that a newline occurs anywhere in the file, let alone in any * particular buffer. If we run out of memory, input is discarded (and the * user warned). */ static void r_buf(FILE *fp, const char *fn) { BF *mark, *tl, *tr; int ch, len, llen; char *p; off_t enomem; tl = NULL; #define BSZ (128 * 1024) for (mark = NULL, enomem = 0; ; ) { /* * Allocate a new block and link it into place in a doubly * linked list. If out of memory, toss the LRU block and * keep going. */ if (enomem || (tl = malloc(sizeof (BF))) == NULL || (tl->l = malloc(BSZ)) == NULL) { if (!mark) err(1, "malloc"); tl = enomem ? tl->next : mark; enomem += tl->len; } else if (mark) { tl->next = mark; tl->prev = mark->prev; mark->prev->next = tl; mark->prev = tl; } else { mark = tl; mark->next = mark->prev = mark; } /* Fill the block with input data. */ for (p = tl->l, len = 0; len < BSZ && (ch = getc(fp)) != EOF; ++len) *p++ = ch; if (ferror(fp)) { ierr(fn); return; } /* * If no input data for this block and we tossed some data, * recover it. */ if (!len && enomem) { enomem -= tl->len; tl = tl->prev; break; } tl->len = len; if (ch == EOF) break; } if (enomem) { warnx("warning: %jd bytes discarded", (intmax_t)enomem); rval = 1; } /* * Step through the blocks in the reverse order read. The last char * is special, ignore whether newline or not. */ for (mark = tl; ; ) { for (p = tl->l + (len = tl->len) - 1, llen = 0; len--; --p, ++llen) if (*p == '\n') { if (llen) { WR(p + 1, llen); llen = 0; } if (tl == mark) continue; for (tr = tl->next; tr->len; tr = tr->next) { WR(tr->l, tr->len); tr->len = 0; if (tr == mark) break; } } tl->len = llen; if ((tl = tl->prev) == mark) break; } tl = tl->next; if (tl->len) { WR(tl->l, tl->len); tl->len = 0; } while ((tl = tl->next)->len) { WR(tl->l, tl->len); tl->len = 0; } } /* * 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. * 4. 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. */ /* * Copyright 2017, Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include "extern.h" int Fflag, fflag, qflag, rflag, rval, no_files; file_info_t *files; static void obsolete(char **); static void usage(void); int main(int argc, char *argv[]) { struct stat sb; const char *fn; FILE *fp; off_t off; enum STYLE style; int i, ch, first; file_info_t *file; char *p; /* * 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) \ 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(argc, argv, "Fb:c:fn:qr")) != -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; break; case 'r': rflag = 1; break; case '?': default: usage(); } argc -= optind; argv += optind; no_files = argc ? argc : 1; /* * 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 = (struct file_info *)malloc(no_files * sizeof (struct file_info)); if (!files) err(1, "Couldn't malloc space for file descriptors."); for (file = files; (fn = *argv++); file++) { file->file_name = strdup(fn); if (! file->file_name) errx(1, "Couldn't malloc space for file name."); if ((file->fp = fopen(file->file_name, "r")) == NULL || fstat(fileno(file->fp), &file->st)) { if (file->fp != NULL) { (void) fclose(file->fp); file->fp = NULL; } if (!Fflag || errno != ENOENT) ierr(file->file_name); } } follow(files, style, off); for (i = 0, file = files; i < no_files; i++, file++) { free(file->file_name); } free(files); } else if (*argv) { for (first = 1; (fn = *argv++); ) { if ((fp = fopen(fn, "r")) == NULL || fstat(fileno(fp), &sb)) { ierr(fn); continue; } if (argc > 1 && !qflag) { (void) printf("%s==> %s <==\n", first ? "" : "\n", fn); first = 0; (void) fflush(stdout); } 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 forward(stdin, fn, style, off, &sb); } exit(rval); } static boolean_t iscount(const char *ap) { char c; if (ap == NULL) { return (B_FALSE); } c = ap[0]; if (c == '+' || c == '-') { c = ap[1]; } return (isdigit(c) ? B_TRUE : B_FALSE); } /* * 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; /* * Legacy Solaris tail supports "+c" "-c", "+l", "-l", * "+b", and "-b" with a default value of 10. We need * to determine here whether or not a count has been * provided after the flag, and create a new, explicit * argument as appropriate. [+-]l isn't allowed to have * any numbers after it, but [+-][bc] can, potentially * in the next command-line argument. We therefore * handle them in two separate cases below. */ case 'l': len = strlen(ap); start = NULL; if (len > 2) { errx(1, "illegal option -- %s", *argv); } /* The only characters following should be flags */ if (len == 2 && !isalpha(ap[1])) { errx(1, "illegal option -- %s", *argv); } if (asprintf(&start, "-%sn%c10", ap + 1, *argv[0]) == -1) { err(1, "asprintf"); } *argv = start; continue; case 'b': case 'c': len = strlen(ap); start = NULL; if (len == 1) { /* * The option is just the flag name. Check if * the next argument is a count, so we know * whether we need to default to 10. */ if (iscount(argv[1])) { ++argv; continue; } else { if (asprintf(&start, "-%c%c10", ap[0], *argv[0]) == -1) { err(1, "asprintf"); } } } else { /* * The option has characters following the c/b. * If the characters following the option are a * count, then we use those. This invocation is * only allowed when '-' is used. * * Otherwise, we need to honor the following * flags, and default to 10. */ if (iscount(ap + 1)) { if (*argv[0] != '-') { errx(1, "illegal option -- %s", *argv); } if (asprintf(&start, "-%c%s", ap[0], ap + 1) == -1) { err(1, "asprintf"); } } else { if (asprintf(&start, "-%s%c%c10", ap + 1, ap[0], *argv[0]) == -1) { err(1, "asprintf"); } } } *argv = start; continue; /* * Options w/ arguments, skip the argument and continue * with the next option. */ 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); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2017, Joyent, Inc. # # # Additional tests borrowed from ksh93 builtin tail test script # (usr/src/cmd/ast/libshell/common/tests/sun_solaris_builtin_tail). Modified # to use /bin/tail rather than the ksh93 builtin. # TAIL=/bin/tail while [[ $# -gt 0 ]]; do case $1 in -o) TAIL=$(cd $(dirname $2); pwd)/$(basename $2) shift 2 ;; *) echo "Usage: sun_solaris_tail.sh" \ "[-o ]" exit 1 ;; esac done # test setup function err_exit { print -u2 -n "\t" print -u2 -r ${Command}[$1]: "${@:2}" (( Errors < 127 && Errors++ )) } alias err_exit='err_exit $LINENO' set -o nounset Command=${0##*/} integer Errors=0 # common functions function isvalidpid { kill -0 ${1} 2>/dev/null && return 0 return 1 } function waitpidtimeout { integer pid=$1 float timeout=$2 float i float -r STEP=0.5 # const (( timeout=timeout/STEP )) for (( i=0 ; i < timeout ; i+=STEP )) ; do isvalidpid ${pid} || break sleep ${STEP} done return 0 } function myintseq { integer i float arg1=$1 float arg2=$2 float arg3=$3 case $# in 1) for (( i=1 ; i <= arg1 ; i++ )) ; do printf "%d\n" i done ;; 2) for (( i=arg1 ; i <= arg2 ; i++ )) ; do printf "%d\n" i done ;; 3) for (( i=arg1 ; i <= arg3 ; i+=arg2 )) ; do printf "%d\n" i done ;; *) print -u2 -f "%s: Illegal number of arguments %d\n" "$0" $# return 1 ;; esac return 0 } # quote input string but use single-backslash that "err_exit" prints # the strings correctly function singlebackslashquote { typeset s s="$(printf "%q\n" "$1")" print -r "$s" return 0 } # quote input string but use double-backslash that "err_exit" prints # the strings correctly function doublebackslashquote { typeset s s="$(printf "%q\n" "$1")" s="${s//\\/\\\\}" print -r "$s" return 0 } # main builtin mktemp || err_exit "mktemp builtin not found" builtin rm || err_exit "rm builtin not found" # builtin tail || err_exit "tail builtin not found" typeset ocwd typeset tmpdir # create temporary test directory ocwd="$PWD" tmpdir="$(mktemp -t -d "test_sun_solaris_builtin_tail.XXXXXXXX")" || err_exit "Cannot create temporary directory" cd "${tmpdir}" || { err_exit "cd ${tmpdir} failed." ; exit $((Errors)) ; } # run tests: # test1: basic tests compound -a testcases=( ( name="reverse_n" input=$'hello\nworld' compound -A tail_args=( [legacy]=( argv=( "-r" ) ) ) expected_output=$'world\nhello' ) ( name="revlist0n" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-0" ) ) # [std_like]=( argv=( "-n" "0" ) ) ) expected_output=$'' ) ( name="revlist0nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-0r" ) ) # [std_like]=( argv=( "-n" "0" "-r" ) ) # [long_options]=( argv=( "--lines" "0" "--reverse" ) ) ) expected_output=$'' ) ( name="revlist1n" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-1" ) ) # [std_like]=( argv=( "-n" "1" ) ) # [long_options]=( argv=( "--lines" "1" ) ) ) expected_output=$'4' ) ( name="revlist1nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-1r" ) ) # [std_like]=( argv=( "-n" "1" "-r" ) ) # [long_options]=( argv=( "--lines" "1" "--reverse" ) ) ) expected_output=$'4' ) ( name="revlist2n" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-2" ) ) # [std_like]=( argv=( "-n" "2" ) ) ) expected_output=$'3\n4' ) ( name="revlist2nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-2r" ) ) # [std_like]=( argv=( "-n" "2" "-r" ) ) ) expected_output=$'4\n3' ) ( name="revlist3nr" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "-3r" ) ) # [std_like]=( argv=( "-n" "3" "-r" ) ) ) expected_output=$'4\n3\n2' ) ( name="revlist2p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+2" ) ) # [std_like]=( argv=( "-n" "+2" ) ) ) expected_output=$'2\n3\n4' ) # Note: following test case trips up legacy Solaris 'tail' as well # ( # name="revlist2pr" # input=$'1\n2\n3\n4' # compound -A tail_args=( # [legacy]=( argv=( "+2r" ) ) # [std_like]=( argv=( "-n" "+2" "-r" ) ) # ) # expected_output=$'4\n3\n2' # ) ( name="revlist3p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+3" ) ) [std_like]=( argv=( "-n" "+3" ) ) ) expected_output=$'3\n4' ) # Note: following test case trips up legacy Solaris 'tail' as well # ( # name="revlist3pr" # input=$'1\n2\n3\n4' # compound -A tail_args=( # [legacy]=( argv=( "+3r" ) ) # [std_like]=( argv=( "-n" "+3" "-r" ) ) # ) # expected_output=$'4\n3' # ) ( name="revlist4p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+4" ) ) # [std_like]=( argv=( "-n" "+4" ) ) ) expected_output=$'4' ) # Note: following test case trips up legacy Solaris 'tail' as well # ( # name="revlist4pr" # input=$'1\n2\n3\n4' # compound -A tail_args=( # [legacy]=( argv=( "+4r" ) ) # [std_like]=( argv=( "-n" "+4" "-r" ) ) # ) # expected_output=$'4' # ) ( name="revlist5p" input=$'1\n2\n3\n4' compound -A tail_args=( [legacy]=( argv=( "+5" ) ) # [std_like]=( argv=( "-n" "+5" ) ) ) expected_output=$'' ) # Note: following test case trips up legacy Solaris 'tail' as well # ( # name="revlist5pr" # input=$'1\n2\n3\n4' # compound -A tail_args=( # [legacy]=( argv=( "+5r" ) ) # [std_like]=( argv=( "-n" "+5" "-r" ) ) # ) # expected_output=$'' # ) ) for testid in "${!testcases[@]}" ; do nameref tc=testcases[${testid}] for argv_variants in "${!tc.tail_args[@]}" ; do nameref argv=tc.tail_args[${argv_variants}].argv output=$( set -o pipefail (trap "" PIPE ; print -r -- "${tc.input}") | $TAIL "${argv[@]}" ) || err_exit "test ${tc.name}/${argv_variants}: command failed with exit code $?" [[ "${output}" == "${tc.expected_output}" ]] || err_exit "test ${tc.name}/${argv_variants}: Expected $(doublebackslashquote "${tc.expected_output}"), got $(doublebackslashquote "${output}")" done done # test2: test "tail -r /dev/null 2>&1 #(( $? == 2 )) || err_exit "expected exit code 2 for unsupported long option, got $?" # test 4: FIFO tests # FIFO test functions # (we use functions here to do propper garbage collection) function test_tail_fifo_1 { typeset tail_cmd="$1" integer i integer tail_pid=-1 # cleanup trap trap "rm -f tailtestfifo tailout" EXIT # create test FIFO mkfifo tailtestfifo ${tail_cmd} -f tailout & tail_pid=$! myintseq 20 >tailtestfifo waitpidtimeout ${tail_pid} 5 if isvalidpid ${tail_pid} ; then err_exit "test_tail_fifo_1: # tail hung (not expected)" kill -KILL ${tail_pid} fi wait || err_exit "tail child returned non-zero exit code=$?" [[ "$(cat tailout)" == $'11\n12\n13\n14\n15\n16\n17\n18\n19\n20' ]] || err_exit "test_tail_fifo_1: Expected $(doublebackslashquote '11\n12\n13\n14\n15\n16\n17\n18\n19\n20'), got $(doublebackslashquote "$(cat tailout)")" return 0 } function test_tail_fifo_2 { typeset tail_cmd="$1" integer i integer tail_pid=-1 # cleanup trap trap "rm -f tailtestfifo tailout" EXIT # create test FIFO mkfifo tailtestfifo ${tail_cmd} -f tailtestfifo >tailout & tail_pid=$! myintseq 14 >tailtestfifo waitpidtimeout ${tail_pid} 5 if isvalidpid ${tail_pid} ; then [[ "$(cat tailout)" == $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14' ]] || err_exit "test_tail_fifo_2: Expected $(doublebackslashquote $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14'), got $(doublebackslashquote "$(cat tailout)")" myintseq 15 >>tailtestfifo waitpidtimeout ${tail_pid} 5 if isvalidpid ${tail_pid} ; then kill -KILL ${tail_pid} else err_exit "test_tail_fifo_2: # tail exit with return code $? (not expected)" fi fi wait || err_exit "tail child returned non-zero exit code=$?" [[ "$(cat tailout)" == $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15' ]] || err_exit "test_tail_fifo_2: Expected $(doublebackslashquote $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15'), got $(doublebackslashquote "$(cat tailout)")" return 0 } # fixme: This should test /bin/tail and /usr/xpg4/bin/tail in Solaris test_tail_fifo_1 "$TAIL" test_tail_fifo_2 "$TAIL" # test 5: "tail -f" tests function followtest1 { typeset -r FOLLOWFILE="followfile.txt" typeset -r OUTFILE="outfile.txt" typeset title="$1" typeset testcmd="$2" typeset usenewline=$3 typeset followstr="" typeset newline="" integer i integer tailchild=-1 if ${usenewline} ; then newline=$'\n' fi rm -f "${FOLLOWFILE}" "${OUTFILE}" print -n "${newline}" > "${FOLLOWFILE}" ${testcmd} -f "${FOLLOWFILE}" >"${OUTFILE}" & (( tailchild=$! )) for (( i=0 ; i < 10 ; i++)) ; do followstr+="${newline}${i}" print -n "${i}${newline}" >>"${FOLLOWFILE}" sleep 2 [[ "$( < "${OUTFILE}")" == "${followstr}" ]] || err_exit "${title}: Expected $(doublebackslashquote "${followstr}"), got "$(doublebackslashquote "$( < "${OUTFILE}")")"" done kill -KILL ${tailchild} 2>/dev/null #kill -TERM ${tailchild} 2>/dev/null waitpidtimeout ${tailchild} 5 if isvalidpid ${tailchild} ; then err_exit "${title}: tail pid=${tailchild} hung." kill -KILL ${tailchild} 2>/dev/null fi wait ${tailchild} 2>/dev/null rm -f "${FOLLOWFILE}" "${OUTFILE}" return 0 } followtest1 "test5a" "$TAIL" true # fixme: later we should test this, too: #followtest1 "test5b" "tail" false #followtest1 "test5c" "/usr/xpg4/bin/tail" true #followtest1 "test5d" "/usr/xpg4/bin/tail" false #followtest1 "test5e" "/bin/tail" true #followtest1 "test5f" "/bin/tail" false # test 6: "tail -f" tests function followtest2 { typeset -r FOLLOWFILE="followfile.txt" typeset -r OUTFILE="outfile.txt" typeset title="$1" typeset testcmd="$2" integer tailchild=-1 rm -f "${FOLLOWFILE}" "${OUTFILE}" myintseq 50000 >"${FOLLOWFILE}" ${testcmd} -n 60000 -f "${FOLLOWFILE}" >"${OUTFILE}" & (( tailchild=$! )) sleep 10 kill -KILL ${tailchild} 2>/dev/null #kill -TERM ${tailchild} 2>/dev/null waitpidtimeout ${tailchild} 5 if isvalidpid ${tailchild} ; then err_exit "${title}: tail pid=${tailchild} hung." kill -KILL ${tailchild} 2>/dev/null fi wait ${tailchild} 2>/dev/null # this tail should be an external process outstr=$(/bin/tail "${OUTFILE}") || err_exit "tail returned non-zero exit code $?" [[ "${outstr}" == 49991*50000 ]] || err_exit "${title}: Expected match for 49991*50000, got "$(singlebackslashquote "${outstr}")"" rm -f "${FOLLOWFILE}" "${OUTFILE}" return 0 } followtest2 "test6a" "$TAIL" followtest2 "test6b" "$TAIL" # fixme: later we should test this, too: #followtest2 "test6c" "/bin/tail" # cleanup cd "${ocwd}" rmdir "${tmpdir}" || err_exit "Cannot remove temporary directory ${tmpdir}". # tests done exit $((Errors)) #!/bin/bash # # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy is of the CDDL is also available via the Internet # at http://www.illumos.org/license/CDDL. # # # Copyright 2010 Chris Love. All rights reserved. # Copyright 2017, Joyent, Inc. # BLOCK="" for i in {1..512}; do BLOCK+="." done checktest() { local actual=$1 local output=$2 local test=$3 if [[ "$actual" != "$output" ]]; then echo "$CMD: test $test: FAIL" echo -e "$CMD: test $test: expected output:\n$output" echo -e "$CMD: test $test: actual output:\n$actual" else echo "$CMD: test $test: pass" fi } checkfail() { printf "foobar" | $PROG $* &> /dev/null if [[ $? -eq 0 ]]; then printf '%s: test "test %s": was supposed to fail\n' "$CMD" "$*" else printf '%s: test "%s": pass\n' "$CMD" "$*" fi } # # Test cases for 'tail', some based on CoreUtils test cases (validated # with legacy Solaris 'tail' and/or xpg4 'tail'). Note that this is designed # to be able to run on BSD systems as well to check our behavior against # theirs (some behavior that is known to be idiosyncratic to illumos is # skipped on non-illumos systems). # PROG=/bin/tail CMD=`basename $0` DIR="" while [[ $# -gt 0 ]]; do case $1 in -x) PROG=/usr/xpg4/bin/tail shift ;; -o) PROG=$2 shift 2 ;; -d) DIR=$2 shift 2 ;; *) echo "Usage: tailtests.sh" \ "[-x][-o ]" \ "[-d ]" exit 1 ;; esac done # # Shut bash up upon receiving a term so we can drop it on our children # without disrupting the output. # trap "exit 0" TERM echo "$CMD: program is $PROG" if [[ $DIR != "" ]]; then echo "$CMD: directory is $DIR" fi o=`echo -e "bcd"` a=`echo -e "abcd" | $PROG +2c` checktest "$a" "$o" 1 o=`echo -e ""` a=`echo "abcd" | $PROG +8c` checktest "$a" "$o" 2 o=`echo -e "abcd"` a=`echo "abcd" | $PROG -9c` checktest "$a" "$o" 3 o=`echo -e "x"` a=`echo -e "x" | $PROG -1l` checktest "$a" "$o" 4 o=`echo -e "\n"` a=`echo -e "x\ny\n" | $PROG -1l` checktest "$a" "$o" 5 o=`echo -e "y\n"` a=`echo -e "x\ny\n" | $PROG -2l` checktest "$a" "$o" 6 o=`echo -e "y"` a=`echo -e "x\ny" | $PROG -1l` checktest "$a" "$o" 7 o=`echo -e "x\ny\n"` a=`echo -e "x\ny\n" | $PROG +1l` checktest "$a" "$o" 8 o=`echo -e "y\n"` a=`echo -e "x\ny\n" | $PROG +2l` checktest "$a" "$o" 9 o=`echo -e "x"` a=`echo -e "x" | $PROG -1` checktest "$a" "$o" 10 o=`echo -e "\n"` a=`echo -e "x\ny\n" | $PROG -1` checktest "$a" "$o" 11 o=`echo -e "y\n"` a=`echo -e "x\ny\n" | $PROG -2` checktest "$a" "$o" 12 o=`echo -e "y"` a=`echo -e "x\ny" | $PROG -1` checktest "$a" "$o" 13 o=`echo -e "x\ny\n"` a=`echo -e "x\ny\n" | $PROG +1` checktest "$a" "$o" 14 o=`echo -e "y\n"` a=`echo -e "x\ny\n" | $PROG +2` checktest "$a" "$o" 15 o=`printf "yyz\n"` a=`printf "xyyyyyyyyyyz\n" | $PROG +10c` checktest "$a" "$o" 16 o=`printf "y\ny\nz\n"` a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG +10l` checktest "$a" "$o" 17 o=`printf "y\ny\ny\ny\ny\ny\ny\ny\ny\nz\n"` a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG -10l` checktest "$a" "$o" 18 a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +10lr` checktest "$a" "$o" 19 a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\nf\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -10lr` checktest "$a" "$o" 20 a=`printf "o\nn\nm\nl\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +10cr` checktest "$a" "$o" 21 a=`printf "o\nn\nm\nl\nk\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -10cr` checktest "$a" "$o" 22 # # For reasons that are presumably as accidental as they are ancient, legacy # (and closed) Solaris tail(1) allows +c, +l and -l to be aliases for +10c, # +10l and -10l, respectively. If we are on SunOS, verify that this silly # behavior is functional. # if [[ `uname -s` == "SunOS" || `uname -s` == "Hammerhead" ]]; then o=`printf "yyz\n"` a=`printf "xyyyyyyyyyyz\n" | $PROG +c` checktest "$a" "$o" 16a o=`printf "y\ny\nz\n"` a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG +l` checktest "$a" "$o" 17a o=`printf "y\ny\ny\ny\ny\ny\ny\ny\ny\nz\n"` a=`printf "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz\n" | $PROG -l` checktest "$a" "$o" 18a a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +lr` checktest "$a" "$o" 19a o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +l -r` checktest "$a" "$o" 19a a=`printf "o\nn\nm\nl\nk\nj\ni\nh\ng\nf\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -lr` checktest "$a" "$o" 20a o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -l -r` checktest "$a" "$o" 20b a=`printf "o\nn\nm\nl\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +cr` checktest "$a" "$o" 21a o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG +c -r` checktest "$a" "$o" 21a a=`printf "o\nn\nm\nl\nk\n"` o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -cr` checktest "$a" "$o" 22a o=`printf "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\n" | $PROG -c -r` checktest "$a" "$o" 22b fi o=`echo -e "c\nb\na"` a=`echo -e "a\nb\nc" | $PROG -r` checktest "$a" "$o" 23 # # Now we want to do a series of follow tests. # if [[ $DIR == "" ]]; then export TMPDIR=/var/tmp tdir=$(mktemp -d -t tailtest.XXXXXXXX || exit 1) else tdir=$(mktemp -d $DIR/tailtest.XXXXXXXX || exit 1) fi follow=$tdir/follow moved=$tdir/follow.moved out=$tdir/out # # First, verify that following works in its most basic sense. # echo -e "a\nb\nc" > $follow $PROG -f $follow > $out 2> /dev/null & child=$! sleep 2 echo -e "d\ne\nf" >> $follow sleep 1 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\n"` a=`cat $out` checktest "$a" "$o" 24 rm $follow # # Now verify that following correctly follows the file being moved. # echo -e "a\nb\nc" > $follow $PROG -f $follow > $out 2> /dev/null & child=$! sleep 2 mv $follow $moved echo -e "d\ne\nf" >> $moved sleep 1 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\n"` a=`cat $out` checktest "$a" "$o" 25 rm $moved # # And now truncation with the new offset being less than the old offset. # echo -e "a\nb\nc" > $follow $PROG -f $follow > $out 2> /dev/null & child=$! sleep 2 echo -e "d\ne\nf" >> $follow sleep 1 echo -e "g\nh\ni" > $follow sleep 1 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"` a=`cat $out` checktest "$a" "$o" 26 rm $follow # # And truncation with the new offset being greater than the old offset. # echo -e "a\nb\nc" > $follow sleep 1 $PROG -f $follow > $out 2> /dev/null & child=$! sleep 2 echo -e "d\ne\nf" >> $follow sleep 1 echo -e "g\nh\ni\nj\nk\nl\nm\no\np\nq" > $follow sleep 1 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\no\np\nq"` a=`cat $out` checktest "$a" "$o" 27 rm $follow # # Verify that we can follow the moved file and correctly see a truncation. # echo -e "a\nb\nc" > $follow $PROG -f $follow > $out 2> /dev/null & child=$! sleep 2 mv $follow $moved echo -e "d\ne\nf" >> $moved sleep 1 echo -e "g\nh\ni\nj\nk\nl\nm\no\np\nq" > $moved sleep 1 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\no\np\nq"` a=`cat $out` checktest "$a" "$o" 28 rm $moved # # Verify that capital-F follow properly deals with truncation # echo -e "a\nb\nc" > $follow $PROG -F $follow > $out 2> /dev/null & child=$! sleep 2 echo -e "d\ne\nf" >> $follow sleep 1 echo -e "g\nh\ni" > $follow sleep 1 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"` a=`cat $out` checktest "$a" "$o" 29 rm $follow # # Verify that capital-F follow _won't_ follow the moved file and will # correctly deal with recreation of the original file. # echo -e "a\nb\nc" > $follow $PROG -F $follow > $out 2> /dev/null & child=$! sleep 2 mv $follow $moved echo -e "x\ny\nz" >> $moved # # At this point, tail is polling on stat'ing the missing file; we need to # be sure to sleep long enough after recreating it to know that it will pick # it up. # echo -e "d\ne\nf" > $follow sleep 5 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\n"` a=`cat $out` checktest "$a" "$o" 30 rm $moved # # Verify that following two files works. # echo -e "one" > $follow echo -e "two" > $moved $PROG -f $follow $moved > $out 2> /dev/null & child=$! sleep 2 echo -e "three" >> $follow sleep 1 echo -e "four" >> $moved sleep 1 echo -e "five" >> $follow sleep 1 kill $child sleep 1 # There is a bug where the content comes before the header lines, # where rlines/mapprint happens before the header. A pain to fix. # In this test, just make sure we see both files change. o="one ==> $follow <== two ==> $moved <== ==> $follow <== three ==> $moved <== four ==> $follow <== five" a=`cat $out` checktest "$a" "$o" 31 rm $follow $moved if [[ `uname -s` == "SunOS" || `uname -s` == "Hammerhead" ]]; then # # Use DTrace to truncate the file between the return from port_get() # and the reassociation of the file object with the port, exposing # any race conditions whereby FILE_TRUNC events are lost. # cat /dev/null > $follow dtrace -c "$PROG -f $follow" -s /dev/stdin > $out < $follow"); system("prun %d", pid); } tick-1sec { system("echo %d >> $follow", j++); } tick-1sec /j == 10/ { exit(0); } EOF o=`echo -e "0\n1\n2\n3\n5\n6\n7\n8\n9\n"` a=`cat $out` checktest "$a" "$o" 31a rm $follow cat /dev/null > $follow dtrace -c "$PROG -f $follow" -s /dev/stdin > $out < $moved"); system("prun %d", pid); } tick-1sec { system("echo %d >> %s", j++, i < 5 ? "$follow" : "$moved"); } tick-1sec /j == 10/ { exit(0); } EOF o=`echo -e "0\n1\n2\n3\n5\n6\n7\n8\n9\n"` a=`cat $out` checktest "$a" "$o" 31b rm $moved # # Verify that -F will deal properly with the file being truncated # not by truncation, but rather via an ftruncate() from logadm. # cat /dev/null > $follow ( $PROG -F $follow > $out ) & child=$! echo -e "a\nb\nc\nd\ne\nf" >> $follow logadm -c $follow sleep 2 echo -e "g\nh\ni" >> $follow sleep 2 kill $child sleep 1 o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"` a=`cat $out` checktest "$a" "$o" 31c fi # # We're now going to test that while we may miss output due to truncations # occurring faster than tail can read, we don't ever repeat output. # cat /dev/null > $follow ( $PROG -f $follow > $out ) & tchild=$! ( let i=0 ; while true; do echo $i > $follow ; sleep 0.1; let i=i+1 ; done ) & child=$! sleep 10 kill $tchild kill $child a=`sort $out | uniq -c | sort -n | tail -1 | awk '{ print $1 }'` o=1 checktest "$a" "$o" 32 # Test different ways of specifying character offsets o=`printf "d\n"` a=`printf "hello\nworld\n" | $PROG -c2` checktest "$a" "$o" 33 a=`printf "hello\nworld\n" | $PROG -c-2` checktest "$a" "$o" 34 a=`printf "hello\nworld\n" | $PROG -c 2` checktest "$a" "$o" 35 a=`printf "hello\nworld\n" | $PROG -c -2` checktest "$a" "$o" 36 a=`printf "hello\nworld\n" | $PROG -2c` checktest "$a" "$o" 37 o=`printf "llo\nworld\n"` a=`printf "hello\nworld\n" | $PROG -c +3` checktest "$a" "$o" 38 a=`printf "hello\nworld\n" | $PROG -c+3` checktest "$a" "$o" 39 a=`printf "hello\nworld\n" | $PROG +3c` checktest "$a" "$o" 40 # Test various ways of specifying block offsets o=`printf "$BLOCK"` a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b1` checktest "$a" "$o" 41 a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b 1` checktest "$a" "$o" 42 a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b -1` checktest "$a" "$o" 43 a=`printf "${BLOCK//./x}$BLOCK" | $PROG -b +2` checktest "$a" "$o" 44 # Test that illegal arguments aren't allowed checkfail +b2 checkfail +c3 checkfail -l3 checkfail -cz checkfail -bz checkfail -nz checkfail -3n checkfail +3n checkfail +n3 checkfail -lfoobar echo "$CMD: completed" exit $errs