14887Schin /*********************************************************************** 24887Schin * * 34887Schin * This software is part of the ast package * 4*8462SApril.Chin@Sun.COM * Copyright (c) 1992-2008 AT&T Intellectual Property * 54887Schin * and is licensed under the * 64887Schin * Common Public License, Version 1.0 * 7*8462SApril.Chin@Sun.COM * by AT&T Intellectual Property * 84887Schin * * 94887Schin * A copy of the License is available at * 104887Schin * http://www.opensource.org/licenses/cpl1.0.txt * 114887Schin * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 124887Schin * * 134887Schin * Information and Software Systems Research * 144887Schin * AT&T Research * 154887Schin * Florham Park NJ * 164887Schin * * 174887Schin * Glenn Fowler <gsf@research.att.com> * 184887Schin * David Korn <dgk@research.att.com> * 194887Schin * * 204887Schin ***********************************************************************/ 214887Schin #pragma prototyped 224887Schin 234887Schin /* 244887Schin * print the tail of one or more files 254887Schin * 264887Schin * David Korn 274887Schin * Glenn Fowler 284887Schin */ 294887Schin 304887Schin static const char usage[] = 314887Schin "+[-?\n@(#)$Id: tail (AT&T Research) 2006-10-18 $\n]" 324887Schin USAGE_LICENSE 334887Schin "[+NAME?tail - output trailing portion of one or more files ]" 344887Schin "[+DESCRIPTION?\btail\b copies one or more input files to standard output " 354887Schin "starting at a designated point for each file. Copying starts " 364887Schin "at the point indicated by the options and is unlimited in size.]" 374887Schin "[+?By default a header of the form \b==> \b\afilename\a\b <==\b " 384887Schin "is output before all but the first file but this can be changed " 394887Schin "with the \b-q\b and \b-v\b options.]" 404887Schin "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \btail\b " 414887Schin "copies from standard input. The start of the file is defined " 424887Schin "as the current offset.]" 434887Schin "[+?The option argument for \b-c\b can optionally be " 444887Schin "followed by one of the following characters to specify a different " 454887Schin "unit other than a single byte:]{" 464887Schin "[+b?512 bytes.]" 474887Schin "[+k?1-kilobyte.]" 484887Schin "[+m?1-megabyte.]" 494887Schin "}" 504887Schin "[+?For backwards compatibility, \b-\b\anumber\a is equivalent to " 514887Schin "\b-n\b \anumber\a and \b+\b\anumber\a is equivalent to " 524887Schin "\b-n -\b\anumber\a.]" 534887Schin 544887Schin "[n:lines]:[lines:=10?Copy \alines\a lines from each file. A negative value " 554887Schin "for \alines\a indicates an offset from the start of the file.]" 564887Schin "[c:bytes]:?[chars?Copy \achars\a bytes from each file. A negative value " 574887Schin "for \achars\a indicates an offset from the start of the file.]" 584887Schin "[f:forever|follow?Loop forever trying to read more characters as the " 594887Schin "end of each file to copy new data. Ignored if reading from a pipe " 604887Schin "or fifo.]" 614887Schin "[h!:headers?Output filename headers.]" 624887Schin "[L:log?When a \b--forever\b file times out via \b--timeout\b, verify that " 634887Schin "the curent file has not been renamed and replaced by another file " 644887Schin "of the same name (a common log file practice) before giving up on " 654887Schin "the file.]" 664887Schin "[q:quiet?Don't output filename headers. For GNU compatibility.]" 674887Schin "[r:reverse?Output lines in reverse order.]" 684887Schin "[s:silent?Don't warn about timeout expiration and log file changes.]" 694887Schin "[t:timeout?Stop checking after \atimeout\a elapses with no additional " 704887Schin "\b--forever\b output. A separate elapsed time is maintained for " 714887Schin "each file operand. There is no timeout by default. The default " 724887Schin "\atimeout\a unit is seconds. \atimeout\a may be a catenation of 1 " 734887Schin "or more integers, each followed by a 1 character suffix. The suffix " 744887Schin "may be omitted from the last integer, in which case it is " 754887Schin "interpreted as seconds. The supported suffixes are:]:[timeout]{" 764887Schin "[+s?seconds]" 774887Schin "[+m?minutes]" 784887Schin "[+h?hours]" 794887Schin "[+d?days]" 804887Schin "[+w?weeks]" 814887Schin "[+M?months]" 824887Schin "[+y?years]" 834887Schin "[+S?scores]" 844887Schin "}" 854887Schin "[v:verbose?Always ouput filename headers.]" 864887Schin "\n" 874887Schin "\n[file ...]\n" 884887Schin "\n" 894887Schin "[+EXIT STATUS?]{" 904887Schin "[+0?All files copied successfully.]" 914887Schin "[+>0?One or more files did not copy.]" 924887Schin "}" 934887Schin "[+SEE ALSO?\bcat\b(1), \bhead\b(1), \brev\b(1)]" 944887Schin ; 954887Schin 964887Schin #include <cmd.h> 974887Schin #include <ctype.h> 984887Schin #include <ls.h> 994887Schin #include <tm.h> 1004887Schin #include <rev.h> 1014887Schin 1024887Schin #define COUNT (1<<0) 1034887Schin #define ERROR (1<<1) 1044887Schin #define FOLLOW (1<<2) 1054887Schin #define HEADERS (1<<3) 1064887Schin #define LOG (1<<4) 1074887Schin #define NEGATIVE (1<<5) 1084887Schin #define POSITIVE (1<<6) 1094887Schin #define REVERSE (1<<7) 1104887Schin #define SILENT (1<<8) 1114887Schin #define TIMEOUT (1<<9) 1124887Schin #define VERBOSE (1<<10) 1134887Schin 1144887Schin #define NOW (unsigned long)time(NiL) 1154887Schin 1164887Schin #define LINES 10 1174887Schin 1184887Schin #ifdef S_ISSOCK 1194887Schin #define FIFO(m) (S_ISFIFO(m)||S_ISSOCK(m)) 1204887Schin #else 1214887Schin #define FIFO(m) S_ISFIFO(m) 1224887Schin #endif 1234887Schin 1244887Schin struct Tail_s; typedef struct Tail_s Tail_t; 1254887Schin 1264887Schin struct Tail_s 1274887Schin { 1284887Schin Tail_t* next; 1294887Schin char* name; 1304887Schin Sfio_t* sp; 1314887Schin Sfoff_t last; 1324887Schin unsigned long expire; 1334887Schin long dev; 1344887Schin long ino; 1354887Schin }; 1364887Schin 1374887Schin /* 1384887Schin * if file is seekable, position file to tail location and return offset 1394887Schin * otherwise, return -1 1404887Schin */ 1414887Schin 1424887Schin static Sfoff_t 1434887Schin tailpos(register Sfio_t* fp, register Sfoff_t number, int delim) 1444887Schin { 1454887Schin register size_t n; 1464887Schin register Sfoff_t offset; 1474887Schin register Sfoff_t first; 1484887Schin register Sfoff_t last; 1494887Schin register char* s; 1504887Schin register char* t; 1514887Schin struct stat st; 1524887Schin 1534887Schin last = sfsize(fp); 1544887Schin if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0) 1554887Schin return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0; 1564887Schin if (delim < 0) 1574887Schin { 1584887Schin if ((offset = last - number) < first) 1594887Schin return first; 1604887Schin return offset; 1614887Schin } 1624887Schin if ((offset = last - SF_BUFSIZE) < first) 1634887Schin offset = first; 1644887Schin for (;;) 1654887Schin { 1664887Schin sfseek(fp, offset, SEEK_SET); 1674887Schin n = last - offset; 1684887Schin if (!(s = sfreserve(fp, n, SF_LOCKR))) 1694887Schin return -1; 1704887Schin t = s + n; 1714887Schin while (t > s) 1724887Schin if (*--t == delim && number-- <= 0) 1734887Schin { 1744887Schin sfread(fp, s, 0); 1754887Schin return offset + (t - s) + 1; 1764887Schin } 1774887Schin sfread(fp, s, 0); 1784887Schin if (offset == first) 1794887Schin break; 1804887Schin last = offset; 1814887Schin if ((offset = last - SF_BUFSIZE) < first) 1824887Schin offset = first; 1834887Schin } 1844887Schin return first; 1854887Schin } 1864887Schin 1874887Schin /* 1884887Schin * this code handles tail from a pipe without any size limits 1894887Schin */ 1904887Schin 1914887Schin static void 1924887Schin pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim) 1934887Schin { 1944887Schin register Sfio_t* out; 1954887Schin register Sfoff_t n; 1964887Schin register Sfoff_t nleft = number; 1974887Schin register size_t a = 2 * SF_BUFSIZE; 1984887Schin register int fno = 0; 1994887Schin Sfoff_t offset[2]; 2004887Schin Sfio_t* tmp[2]; 2014887Schin 2024887Schin if (delim < 0 && a > number) 2034887Schin a = number; 2044887Schin out = tmp[0] = sftmp(a); 2054887Schin tmp[1] = sftmp(a); 2064887Schin offset[0] = offset[1] = 0; 2074887Schin while ((n = sfmove(infile, out, number, delim)) > 0) 2084887Schin { 2094887Schin offset[fno] = sftell(out); 2104887Schin if ((nleft -= n) <= 0) 2114887Schin { 2124887Schin out = tmp[fno= !fno]; 2134887Schin sfseek(out, (Sfoff_t)0, SEEK_SET); 2144887Schin nleft = number; 2154887Schin } 2164887Schin } 2174887Schin if (nleft == number) 2184887Schin { 2194887Schin offset[fno] = 0; 2204887Schin fno= !fno; 2214887Schin } 2224887Schin sfseek(tmp[0], (Sfoff_t)0, SEEK_SET); 2234887Schin 2244887Schin /* 2254887Schin * see whether both files are needed 2264887Schin */ 2274887Schin 2284887Schin if (offset[fno]) 2294887Schin { 2304887Schin sfseek(tmp[1], (Sfoff_t)0, SEEK_SET); 2314887Schin if ((n = number - nleft) > 0) 2324887Schin sfmove(tmp[!fno], NiL, n, delim); 2334887Schin if ((n = offset[!fno] - sftell(tmp[!fno])) > 0) 2344887Schin sfmove(tmp[!fno], outfile, n, -1); 2354887Schin } 2364887Schin else 2374887Schin fno = !fno; 2384887Schin sfmove(tmp[fno], outfile, offset[fno], -1); 2394887Schin sfclose(tmp[0]); 2404887Schin sfclose(tmp[1]); 2414887Schin } 2424887Schin 2434887Schin /* 2444887Schin * (re)initialize a tail stream 2454887Schin */ 2464887Schin 2474887Schin static int 2484887Schin init(Tail_t* tp, Sfoff_t number, int delim, int flags) 2494887Schin { 2504887Schin Sfoff_t offset; 2514887Schin struct stat st; 2524887Schin 2534887Schin if (tp->sp) 2544887Schin { 2554887Schin offset = 0; 2564887Schin if (tp->sp == sfstdin) 2574887Schin tp->sp = 0; 2584887Schin } 2594887Schin else if (!number) 2604887Schin offset = 0; 2614887Schin else 2624887Schin offset = 1; 2634887Schin if (!tp->name || streq(tp->name, "-")) 2644887Schin { 2654887Schin tp->name = "/dev/stdin"; 2664887Schin tp->sp = sfstdin; 2674887Schin } 2684887Schin else if (!(tp->sp = sfopen(tp->sp, tp->name, "r"))) 2694887Schin { 2704887Schin error(ERROR_system(0), "%s: cannot open", tp->name); 2714887Schin return -1; 2724887Schin } 2734887Schin sfset(tp->sp, SF_SHARE, 0); 2744887Schin if (offset) 2754887Schin { 2764887Schin if ((offset = tailpos(tp->sp, number, delim)) < 0) 2774887Schin { 2784887Schin error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name); 2794887Schin goto bad; 2804887Schin } 2814887Schin sfseek(tp->sp, offset, SEEK_SET); 2824887Schin } 2834887Schin tp->last = offset; 2844887Schin if (flags & LOG) 2854887Schin { 2864887Schin if (fstat(sffileno(tp->sp), &st)) 2874887Schin { 2884887Schin error(ERROR_system(0), "%s: cannot stat", tp->name); 2894887Schin goto bad; 2904887Schin } 2914887Schin tp->dev = st.st_dev; 2924887Schin tp->ino = st.st_ino; 2934887Schin } 2944887Schin return 0; 2954887Schin bad: 2964887Schin if (tp->sp != sfstdin) 2974887Schin sfclose(tp->sp); 2984887Schin tp->sp = 0; 2994887Schin return -1; 3004887Schin } 3014887Schin 3024887Schin /* 3034887Schin * convert number with validity diagnostics 3044887Schin */ 3054887Schin 3064887Schin static intmax_t 3074887Schin num(register const char* s, char** e, int* f, int o) 3084887Schin { 3094887Schin intmax_t number; 3104887Schin char* t; 3114887Schin int c; 3124887Schin 3134887Schin *f &= ~(ERROR|NEGATIVE|POSITIVE); 3144887Schin if ((c = *s) == '-') 3154887Schin { 3164887Schin *f |= NEGATIVE; 3174887Schin s++; 3184887Schin } 3194887Schin else if (c == '+') 3204887Schin { 3214887Schin *f |= POSITIVE; 3224887Schin s++; 3234887Schin } 3244887Schin while (*s == '0' && isdigit(*(s + 1))) 3254887Schin s++; 3264887Schin errno = 0; 3274887Schin number = strtonll(s, &t, NiL, 0); 3284887Schin if (!o && t > s && *(t - 1) == 'l') 3294887Schin t--; 3304887Schin if (t == s) 3314887Schin number = LINES; 3324887Schin if (o && *t) 3334887Schin { 3344887Schin number = 0; 3354887Schin *f |= ERROR; 3364887Schin error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s); 3374887Schin } 3384887Schin else if (errno) 3394887Schin { 3404887Schin *f |= ERROR; 3414887Schin if (o) 3424887Schin error(2, "-%c: %s: invalid numeric argument -- out of range", o, s); 3434887Schin else 3444887Schin error(2, "%s: invalid numeric argument -- out of range", s); 3454887Schin } 3464887Schin else 3474887Schin { 3484887Schin *f |= COUNT; 3494887Schin if (c == '-') 3504887Schin number = -number; 3514887Schin } 3524887Schin if (e) 3534887Schin *e = t; 3544887Schin return number; 3554887Schin } 3564887Schin 3574887Schin int 3584887Schin b_tail(int argc, char** argv, void* context) 3594887Schin { 3604887Schin static const char header_fmt[] = "\n==> %s <==\n"; 3614887Schin 3624887Schin register Sfio_t* ip; 3634887Schin register int n; 3644887Schin register int i; 3654887Schin register int delim = '\n'; 3664887Schin int flags = HEADERS; 3674887Schin char* s; 3684887Schin char* t; 3694887Schin char* r; 3704887Schin char* e; 3714887Schin char* file; 3724887Schin Sfoff_t offset; 3734887Schin Sfoff_t number = LINES; 3744887Schin unsigned long timeout = 0; 3754887Schin struct stat st; 3764887Schin const char* format = header_fmt+1; 3774887Schin size_t z; 3784887Schin Sfio_t* op; 3794887Schin register Tail_t* fp; 3804887Schin register Tail_t* pp; 3814887Schin register Tail_t* hp; 3824887Schin Tail_t* files; 3834887Schin 3844887Schin cmdinit(argc, argv, context, ERROR_CATALOG, 0); 3854887Schin for (;;) 3864887Schin { 3874887Schin switch (n = optget(argv, usage)) 3884887Schin { 3894887Schin case 'c': 3904887Schin delim = -1; 3914887Schin if (opt_info.arg && *opt_info.arg=='f' && !*(opt_info.arg+1)) 3924887Schin { 3934887Schin flags |= FOLLOW; 3944887Schin continue; 3954887Schin } 3964887Schin /*FALLTHROUGH*/ 3974887Schin case 'n': 3984887Schin flags |= COUNT; 3994887Schin if (s = opt_info.arg) 4004887Schin number = num(s, &s, &flags, n); 4014887Schin else 4024887Schin { 4034887Schin number = LINES; 4044887Schin flags &= ~(ERROR|NEGATIVE|POSITIVE); 4054887Schin s = ""; 4064887Schin } 4074887Schin if (n=='c' && *s=='f') 4084887Schin { 4094887Schin s++; 4104887Schin flags |= FOLLOW; 4114887Schin } 4124887Schin if (flags & ERROR) 4134887Schin continue; 4144887Schin if (flags & (NEGATIVE|POSITIVE)) 4154887Schin number = -number; 4164887Schin if (opt_info.option[0]=='+') 4174887Schin number = -number; 4184887Schin continue; 4194887Schin case 'f': 4204887Schin flags |= FOLLOW; 4214887Schin continue; 4224887Schin case 'h': 4234887Schin if (opt_info.num) 4244887Schin flags |= HEADERS; 4254887Schin else 4264887Schin flags &= ~HEADERS; 4274887Schin continue; 4284887Schin case 'L': 4294887Schin flags |= LOG; 4304887Schin continue; 4314887Schin case 'q': 4324887Schin flags &= ~HEADERS; 4334887Schin continue; 4344887Schin case 'r': 4354887Schin flags |= REVERSE; 4364887Schin continue; 4374887Schin case 's': 4384887Schin flags |= SILENT; 4394887Schin continue; 4404887Schin case 't': 4414887Schin flags |= TIMEOUT; 4424887Schin timeout = strelapsed(opt_info.arg, &s, 1); 4434887Schin if (*s) 4444887Schin error(ERROR_exit(1), "%s: invalid elapsed time", opt_info.arg); 4454887Schin continue; 4464887Schin case 'v': 4474887Schin flags |= VERBOSE; 4484887Schin continue; 4494887Schin case ':': 4504887Schin /* handle old style arguments */ 4514887Schin r = s = argv[opt_info.index]; 4524887Schin number = num(s, &t, &flags, 0); 4534887Schin for (;;) 4544887Schin { 4554887Schin switch (*t++) 4564887Schin { 4574887Schin case 0: 4584887Schin opt_info.offset = t - r - 1; 4594887Schin if (number) 4604887Schin number = -number; 4614887Schin break; 4624887Schin case 'c': 4634887Schin delim = -1; 4644887Schin continue; 4654887Schin case 'f': 4664887Schin flags |= FOLLOW; 4674887Schin continue; 4684887Schin case 'l': 4694887Schin delim = '\n'; 4704887Schin continue; 4714887Schin case 'r': 4724887Schin flags |= REVERSE; 4734887Schin continue; 4744887Schin default: 4754887Schin error(2, "%s: invalid suffix", t - 1); 4764887Schin opt_info.offset = strlen(r); 4774887Schin break; 4784887Schin } 4794887Schin break; 4804887Schin } 4814887Schin continue; 4824887Schin case '?': 4834887Schin error(ERROR_usage(2), "%s", opt_info.arg); 4844887Schin break; 4854887Schin } 4864887Schin break; 4874887Schin } 4884887Schin argv += opt_info.index; 4894887Schin if (!*argv) 4904887Schin { 4914887Schin flags &= ~HEADERS; 4924887Schin if (fstat(0, &st)) 4934887Schin error(ERROR_system(0), "/dev/stdin: cannot stat"); 4944887Schin else if (FIFO(st.st_mode)) 4954887Schin flags &= ~FOLLOW; 4964887Schin } 4974887Schin else if (!*(argv + 1)) 4984887Schin flags &= ~HEADERS; 4994887Schin if (flags & REVERSE) 5004887Schin { 5014887Schin if (delim < 0) 5024887Schin error(2, "--reverse requires line mode"); 5034887Schin else if (!(flags & COUNT)) 5044887Schin number = 0; 5054887Schin flags &= ~FOLLOW; 5064887Schin } 5074887Schin if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT) 5084887Schin { 5094887Schin flags &= ~TIMEOUT; 5104887Schin timeout = 0; 5114887Schin error(ERROR_warn(0), "--timeout ignored for --noforever"); 5124887Schin } 5134887Schin if ((flags & (LOG|TIMEOUT)) == LOG) 5144887Schin { 5154887Schin flags &= ~LOG; 5164887Schin error(ERROR_warn(0), "--log ignored for --notimeout"); 5174887Schin } 5184887Schin if (error_info.errors) 5194887Schin error(ERROR_usage(2), "%s", optusage(NiL)); 5204887Schin if (flags & FOLLOW) 5214887Schin { 5224887Schin if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t)))) 5234887Schin error(ERROR_system(1), "out of space"); 5244887Schin files = 0; 5254887Schin s = *argv; 5264887Schin do 5274887Schin { 5284887Schin fp->name = s; 5294887Schin fp->sp = 0; 5304887Schin if (!init(fp, number, delim, flags)) 5314887Schin { 5324887Schin fp->expire = timeout ? (NOW + timeout + 1) : 0; 5334887Schin if (files) 5344887Schin pp->next = fp; 5354887Schin else 5364887Schin files = fp; 5374887Schin pp = fp; 5384887Schin fp++; 5394887Schin } 5404887Schin } while (s && (s = *++argv)); 5414887Schin if (!files) 5424887Schin return error_info.errors != 0; 5434887Schin pp->next = 0; 5444887Schin hp = 0; 5454887Schin for (;;) 5464887Schin { 5474887Schin if (sfsync(sfstdout)) 5484887Schin error(ERROR_system(1), "write error"); 5494887Schin sleep(1); 5504887Schin n = 0; 5514887Schin pp = 0; 5524887Schin fp = files; 5534887Schin while (fp) 5544887Schin { 5554887Schin if (fstat(sffileno(fp->sp), &st)) 5564887Schin error(ERROR_system(0), "%s: cannot stat", fp->name); 5574887Schin else if (st.st_size > fp->last) 5584887Schin { 5594887Schin n = 1; 5604887Schin if (timeout) 5614887Schin fp->expire = NOW + timeout; 5624887Schin z = st.st_size - fp->last; 5634887Schin i = 0; 5644887Schin if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1)) 5654887Schin { 5664887Schin r = 0; 5674887Schin for (e = (t = s) + z; t < e; t++) 5684887Schin if (*t == '\n') 5694887Schin r = t; 5704887Schin if (r || i && (r = e)) 5714887Schin { 5724887Schin if ((flags & (HEADERS|VERBOSE)) && hp != fp) 5734887Schin { 5744887Schin hp = fp; 5754887Schin sfprintf(sfstdout, format, fp->name); 5764887Schin format = header_fmt; 5774887Schin } 5784887Schin z = r - s + 1; 5794887Schin fp->last += z; 5804887Schin sfwrite(sfstdout, s, z); 5814887Schin } 5824887Schin else 5834887Schin z = 0; 5844887Schin sfread(fp->sp, s, z); 5854887Schin } 5864887Schin goto next; 5874887Schin } 5884887Schin else if (!timeout || fp->expire > NOW) 5894887Schin goto next; 5904887Schin else 5914887Schin { 5924887Schin if (flags & LOG) 5934887Schin { 5944887Schin i = 3; 5954887Schin while (--i && stat(fp->name, &st)) 5964887Schin sleep(1); 5974887Schin if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags)) 5984887Schin { 5994887Schin if (!(flags & SILENT)) 6004887Schin error(ERROR_warn(0), "%s: log file change", fp->name); 6014887Schin fp->expire = NOW + timeout; 6024887Schin goto next; 6034887Schin } 6044887Schin } 6054887Schin if (!(flags & SILENT)) 6064887Schin error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1)); 6074887Schin } 6084887Schin if (fp->sp && fp->sp != sfstdin) 6094887Schin sfclose(fp->sp); 6104887Schin if (pp) 6114887Schin pp = pp->next = fp->next; 6124887Schin else if (!(files = files->next)) 6134887Schin return error_info.errors != 0; 6144887Schin fp = fp->next; 6154887Schin continue; 6164887Schin next: 6174887Schin pp = fp; 6184887Schin fp = fp->next; 6194887Schin } 6204887Schin } 6214887Schin } 6224887Schin else 6234887Schin { 6244887Schin if (file = *argv) 6254887Schin argv++; 6264887Schin do 6274887Schin { 6284887Schin if (!file || streq(file, "-")) 6294887Schin { 6304887Schin file = "/dev/stdin"; 6314887Schin ip = sfstdin; 6324887Schin } 6334887Schin else if (!(ip = sfopen(NiL, file, "r"))) 6344887Schin { 6354887Schin error(ERROR_system(0), "%s: cannot open", file); 6364887Schin continue; 6374887Schin } 6384887Schin if (flags & (HEADERS|VERBOSE)) 6394887Schin sfprintf(sfstdout, format, file); 6404887Schin format = header_fmt; 6414887Schin if (number < 0 || !number && (flags & POSITIVE)) 6424887Schin { 6434887Schin sfset(ip, SF_SHARE, !(flags & FOLLOW)); 6444887Schin if (number < -1) 6454887Schin sfmove(ip, NiL, -number - 1, delim); 6464887Schin if (flags & REVERSE) 6474887Schin rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR)); 6484887Schin else 6494887Schin sfmove(ip, sfstdout, SF_UNBOUND, -1); 6504887Schin } 6514887Schin else 6524887Schin { 6534887Schin sfset(ip, SF_SHARE, 0); 6544887Schin if ((offset = tailpos(ip, number, delim)) >= 0) 6554887Schin { 6564887Schin if (flags & REVERSE) 6574887Schin rev_line(ip, sfstdout, offset); 6584887Schin else 6594887Schin { 6604887Schin sfseek(ip, offset, SEEK_SET); 6614887Schin sfmove(ip, sfstdout, SF_UNBOUND, -1); 6624887Schin } 6634887Schin } 6644887Schin else 6654887Schin { 6664887Schin op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout; 6674887Schin pipetail(ip, op, number, delim); 6684887Schin if (flags & REVERSE) 6694887Schin { 6704887Schin sfseek(op, (Sfoff_t)0, SEEK_SET); 6714887Schin rev_line(op, sfstdout, (Sfoff_t)0); 6724887Schin sfclose(op); 6734887Schin } 6744887Schin flags = 0; 6754887Schin } 6764887Schin } 6774887Schin if (ip != sfstdin) 6784887Schin sfclose(ip); 6794887Schin } while (file = *argv++); 6804887Schin } 6814887Schin return error_info.errors != 0; 6824887Schin } 683